-
Notifications
You must be signed in to change notification settings - Fork 18
/
Copy pathZipDatasetSource.java
429 lines (388 loc) · 14 KB
/
ZipDatasetSource.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
/*
* %Z%%W% %I%
*
* =========================================================================
* Licensed Materials - Property of IBM
* "Restricted Materials of IBM"
* Copyright IBM Corp. 2008. All Rights Reserved
*
* DISCLAIMER:
* The following [enclosed] code is sample code created by IBM
* Corporation. This sample code is not part of any standard IBM product
* and is provided to you solely for the purpose of assisting you in the
* development of your applications. The code is provided 'AS IS',
* without warranty of any kind. IBM shall not be liable for any damages
* arising out of your use of the sample code, even if they have been
* advised of the possibility of such damages.
* =========================================================================
*/
package com.ibm.jzos.sample;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.util.Iterator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import com.ibm.jzos.CatalogSearch;
import com.ibm.jzos.CatalogSearchField;
import com.ibm.jzos.PdsDirectory;
import com.ibm.jzos.RcException;
import com.ibm.jzos.ZFile;
import com.ibm.jzos.ZUtil;
/**
* A class that is used by {@link ZipDatasets} to handle the creation of
* zip file entries for z/OS datasets.
* <p/>
* Instances of this class are constructed using a dataset name or pattern,
* which can include:
* <ul>
* <li>A sequential dataset or PDS name: //A.B.C </li>
* <li>A dataset pattern name: //A.*.D
* as defined by the z/OS Catalog Search facility (IGGCSI00).
* See {@link CatalogSearch} for more information. </li>
* <li>A PDS member name: //A.B.C(MEM) </li>
* <li>A PDS member pattern: //A.B.C(D*X) </li>
* <li>A DD name: //DD:XYZ which might refer to a sequential dataset, or PDS,
* or concatenation. </li>
* <li>A DD name and member: //DD:XYZ(MEM) </li>
* <li>A DD name and member pattern: //DD:XYZ(D*X) </li>
* </ul>
* The leading "//" prefix may be omitted and names are case insensitive.
* <p/>
* Each dataset is zipped to the ZipOutputStream by reading the source
* dataset as text encoded in the default
* EBCDIC codepage ({@link ZUtil#getDefaultPlatformEncoding()})
* and then writing the text to ZipOutputStream encoded using
* the supplied target encoding.
* <p/>
* The name given to each entry is the actual MVS dataset name in upper case.
* If the entry is for a PDS member, then the dataset name is used as
* a directory name followed by the member name as a file name.
* <p/>
* @see ZipDatasets ZipDatasets the main class used to zip z/OS datasets
* @see #addTo(ZipOutputStream, String)
* @since 2.3.0
*/
public class ZipDatasetSource {
// Some constants used to build regular expression
static final String SLASH_SLASH_PREFIX = "//";
static final String DD_PREFIX = "DD:";
static final String DSNAME_CHAR = "[\\w[#\\$\\.]]";
static final String DSNAME_PATTERN_CHAR = "[\\w[#\\$\\.\\*]]";
static final String MEMBER_CHAR = "[\\w[#\\$]";
static final String MEMBER__PATTERN_CHAR = "[\\w[#\\$\\*]]";
static final String DSNAME_PIECE = DSNAME_CHAR + "{1,44}";
static final String DDNAME_PIECE = DD_PREFIX + "\\w{1,8}";
/** A dataset pattern has at least one asterisk, but may not start with an asterisk */
static final String DSNAME_PATTERN = "^"
+ DSNAME_CHAR
+ "+\\*"
+ DSNAME_PATTERN_CHAR
+ "*$";
/** A member pattern is a dataset(pat) or DD:name(pat) where mpat is a member name
or pattern that includes an asterisk */
static final String DSNAME_WITH_MEMBER_OR_PATTERN =
"^(" + DSNAME_PIECE + "|" + DDNAME_PIECE + ")"
+ "\\("
+ "(" + MEMBER__PATTERN_CHAR + "+)"
+ "\\)$";
/** The buffer size used to read/write blocks of data */
static final int BUFSIZE = 64 * 1024;
/**
* The name of the input dataset source.
* Uppercased, with any "//" prefix removed.
*/
private String name;
/**
* The name of the member or member pattern name, which is
* extracted from the original dataset source name.
* May be null if no member name or pattern was given
*/
private String memberPattern;
/**
* Construct an instance given a dataset/pattern name.
* We also convert the name to uppercase and drop any
* "//" prefix.
*/
public ZipDatasetSource(String nm) {
name = nm.toUpperCase();
if (name.startsWith(SLASH_SLASH_PREFIX)) {
name = name.substring(SLASH_SLASH_PREFIX.length());
}
}
/**
* Answer the dataset/pattern name.
*/
public String getName() {
return name;
}
/**
* Add one or more entries to the given ZipOutputStream for the
* dataset or datasets described by this ZipDatasetSource.
* <p/>
* @param zipOutStream the output ZipOutputStream
* @param targetEncoding the codepage used to encode the data written to the zipOutStream
* @throws IOException
*/
public void addTo(ZipOutputStream zipOutStream, String targetEncoding) throws IOException {
if (name.matches(DSNAME_PATTERN)) {
// Process a dataset name that includes a pattern character ('*')
addMatchingDatasets(zipOutStream, targetEncoding);
} else {
// If a member name or member pattern was given,
// split it out from the dataset name
Pattern mempat = Pattern.compile(DSNAME_WITH_MEMBER_OR_PATTERN);
Matcher matcher = mempat.matcher(name);
if (matcher.matches()) {
name = matcher.group(1); // get the dsname | dd:name
memberPattern = matcher.group(2); // get the member/pattern
}
// Process the dataset, pds, or dataset(member)
addDatasetOrPds(zipOutStream, targetEncoding);
}
}
/**
* Add a single dataset, single member, complete PDS, or a pattern of PDS members
* to the given ZipOutputStream.
* <p/>
* @param zipOutStream the target ZipOutputStream
* @param targetEncoding the target text encoding
* @throws IOException
*/
private void addDatasetOrPds(ZipOutputStream zipOutStream, String targetEncoding)
throws IOException
{
// allocate a DD to point to the base dsname (or return the name if //DD:name given)
String ddname = allocDD();
// If we are given no member name or a member name pattern,
// we try to process the dataset as a PDS directory.
// This fails if the dataset was not a PDS.
PdsDirectory pdsDir = null;
if (memberPattern == null || memberPattern.indexOf('*') >= 0) {
try {
pdsDir = new PdsDirectory(SLASH_SLASH_PREFIX + DD_PREFIX + ddname);
} catch (IOException ioe) { } // fall through with pdsDir == null
}
// If its not a PdsDirectory, then assume that it is a regular dataset or single member
if (pdsDir == null) {
addDatasetOrMember(zipOutStream, targetEncoding, ddname, memberPattern);
return;
}
// Process a PDS directory...
// If we are given a pattern string to filter members, then build
// a regular expression to use.
Pattern memberRegex = null;
if (memberPattern != null) {
memberRegex = makeRegexPattern(memberPattern);
}
// Loop over the entries in the directory and add all/matching
// members to the zipOutStream
try {
for (Iterator i=pdsDir.iterator(); i.hasNext(); ) {
PdsDirectory.MemberInfo member = (PdsDirectory.MemberInfo)i.next();
String memberName = member.getName();
if (memberRegex == null || memberRegex.matcher(memberName).matches()) {
addDatasetOrMember(zipOutStream, targetEncoding, ddname, memberName);
}
}
} finally {
// Faithfully close the directory and free the DD when we are done,
// even if an exception is thrown.
try {
pdsDir.close();
} catch (IOException ignore) {}
freeDD(ddname);
}
}
/**
* Add an single {@link ZipEntry} for a single dataset or member.
*
* @param zipOutStream the target ZipOutputStream
* @param targetEncoding the target text encoding
* @param ddname the DD allocated to the dataset or member
* @param memberName the member name; used to create the entry name
* @throws IOException
*/
private void addDatasetOrMember(ZipOutputStream zipOutStream, String targetEncoding,
String ddname, String memberName) throws IOException {
Reader reader = null;
try {
reader = openInputFile(ddname, memberName);
// Construct the name of the Zip entry that we will add
String entryName = memberName == null
? name
: name + "/" + memberName;
// Start a new ZipEntry in the Zip file,
// copy the dataset/member data into the Zip entry,
// and close the entry
ZipEntry entry = new ZipEntry(entryName);
zipOutStream.putNextEntry(entry);
copyData(reader, zipOutStream, targetEncoding);
zipOutStream.closeEntry();
System.out.println(" added: " + entryName
+ " (" + entry.getSize() + " -> " + entry.getCompressedSize() + ")");
} finally {
closeInputFile(reader);
freeDD(ddname);
}
}
/**
* Given a dataset source name that included wild card ('*') characters,
* use the z/OS CatalogSearch facility (IGGCSI00) to find and process all
* of the matching sequential or GDS datasets that match.
* @param zipOutStream
* @param targetEncoding
* @throws IOException
*/
private void addMatchingDatasets(ZipOutputStream zipOutStream, String targetEncoding) throws IOException {
CatalogSearch catSearch = new CatalogSearch(name);
catSearch.setEntryTypes("AH"); // only NON-VSAM and Generation Datasets
catSearch.addFieldName("ENTNAME");
catSearch.search();
while (catSearch.hasNext()) {
CatalogSearch.Entry entry = (CatalogSearch.Entry)catSearch.next();
if (entry.isDatasetEntry()) {
CatalogSearchField field = entry.getField("ENTNAME");
String dsn = field.getFString().trim();
// make a new ZipSource with the next dsn and add it
ZipDatasetSource source = new ZipDatasetSource(dsn);
source.addTo(zipOutStream, targetEncoding);
}
}
}
/**
* Make a regular expression pattern than matches the given
* member name pattern that includes literal characters and
* zero or more asterisks.
*/
private Pattern makeRegexPattern(String memberPattern) {
StringBuffer patBuf = new StringBuffer("^");
for (int i=0; i<memberPattern.length(); i++) {
char c = memberPattern.charAt(i);
switch (c) {
case '*':
patBuf.append(".*");
break;
case '$':
patBuf.append("\\$");
break;
default:
patBuf.append(c);
}
}
patBuf.append('$');
return Pattern.compile(patBuf.toString());
}
/**
* Copy data from a reader to a ZipOutputStream.
* @param reader a Reader open on the input dataset/member in the default EBCDIC encoding
* @param zipOutStream the target ZipOutputStram
* @param targetEncoding the target encoding for the ZipOutputStream
* @throws IOException
* @throws UnsupportedEncodingException
*/
private void copyData(Reader reader,
ZipOutputStream zipOutStream,
String targetEncoding)
throws IOException, UnsupportedEncodingException
{
char[] cbuf = new char[BUFSIZE];
int nRead;
// wrap the zipOutputStream in a Writer that encodes to the target encoding
OutputStreamWriter osw = new OutputStreamWriter(zipOutStream, targetEncoding);
while ((nRead = reader.read(cbuf)) != -1) {
osw.write(cbuf, 0, nRead);
}
osw.flush(); // flush any buffered data to the ZipOutputStream
}
/**
* Allocate a new DD with DISP=SHR to point to the source dataset,
* or of a DD:name was given, return the ddname.
* @return the ddname given/allocated
* @throws IOException
*/
private String allocDD() throws IOException {
String ddname = null;
// See if a DD:name was given
if (name.startsWith(DD_PREFIX)) {
ddname = name.substring(DD_PREFIX.length());
return ddname;
}
// Otherwise we allocate a temporary DD to the given dataset
// using DISP=SHR
try {
// get a new SYSnnnnnn DD name allocated to dummy
ddname = ZFile.allocDummyDDName();
// reallocate it to the dataset with DISP=SHR
ZFile.bpxwdyn("alloc fi("+ddname+") da("+name+") shr reuse msg(2)");
return ddname;
} catch (RcException rce) {
freeDD(ddname); // free the temp dd
throw new IOException("Unable to allocate input dataset: "
+ name
+ " - "
+ rce);
}
}
/**
* Do our best to free a DD that we allocated
* @param ddname
*/
private void freeDD(String ddname) {
if (ddname == null || name.startsWith(DD_PREFIX)) {
return;
}
try {
// Omit the 'msg' keyword to suppress error messages.
// We might not actually be able to free the DD if
// if is still open as a PDS directory
ZFile.bpxwdyn("free fi("+ddname+")");
} catch(RcException ignore) {}
}
/**
* Open a Reader to point to the previously allocated
* DD and optionally a given member. The encoding for the
* reader is set to the default EBCDIC encoding
* (see {@link ZUtil#getDefaultPlatformEncoding()})
* <p/>
* @param ddname the DD allocated to the dataset
* @param memberName if not null, the member name to open
* @return a Reader
* @throws IOException
*/
private Reader openInputFile(String ddname, String memberName) throws IOException {
String sourceEncoding = ZUtil.getDefaultPlatformEncoding();
String filename = SLASH_SLASH_PREFIX + DD_PREFIX
+ ddname
+ (memberName==null ? "": "("+memberName+")");
// We open the file in text mode, so that new-line characters are inserted
// at record boundaries and trailing spaces are removed from records.
ZFile zFile = new ZFile(filename, "rt");
// If the open file's actual filename can be determined, use it as our name
String actualFileName = zFile.getActualFilename();
if (actualFileName != null) {
name = actualFileName;
}
// Strip the member name off of the actual file name
int ilparen = name.indexOf('(');
if (ilparen > 0) {
name = name.substring(0,ilparen);
}
InputStream is = zFile.getInputStream();
return new InputStreamReader(is, sourceEncoding);
}
/**
* Close the input reader.
*/
private void closeInputFile(Reader reader) {
if (reader == null) return;
try {
reader.close();
} catch (IOException ignore) {}
}
}