View Javadoc

1   /*******************************************************************************
2    * Copyright (c) 2009 LegSem.
3    * All rights reserved. This program and the accompanying materials
4    * are made available under the terms of the GNU Lesser Public License v2.1
5    * which accompanies this distribution, and is available at
6    * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
7    * 
8    * Contributors:
9    *     LegSem - initial API and implementation
10   ******************************************************************************/
11  package com.legstar.zosjes;
12  
13  import java.io.ByteArrayOutputStream;
14  import java.io.File;
15  import java.io.FileInputStream;
16  import java.io.IOException;
17  import java.io.OutputStream;
18  import java.util.regex.Matcher;
19  import java.util.regex.Pattern;
20  
21  import org.apache.commons.net.ftp.FTPClient;
22  import org.apache.commons.net.ftp.FTPClientConfig;
23  import org.apache.commons.net.ftp.FTPReply;
24  
25  /**
26   * Manages an FTP connection to a z/OS server.
27   * <p/>
28   * Makes it simple to upload ASCII files and submit JCL for execution.
29   *
30   */
31  public class FtpZosClient {
32  	
33  	/** Apache commons net FTP client.*/
34  	FTPClient _ftpClient;
35  	
36  	/** The expected string reply from FTP z/OS on job submission. */
37  	private static final String SUBMIT_REPLY = "250-It is known to JES as ";
38  	
39  	/** Condition code the way it appears in jobs held output.*/
40  	private static final Pattern COND_CODE_PATTERN =
41  		Pattern.compile("COND CODE (\\d{4})", Pattern.CASE_INSENSITIVE);
42  
43  	/**
44  	 * No-arg constructor.
45  	 */
46  	public FtpZosClient() {
47  		_ftpClient = new FTPClient();
48  		FTPClientConfig ftpConf = new FTPClientConfig(FTPClientConfig.SYST_MVS);
49  		ftpConf.setServerTimeZoneId("GMT");
50  		_ftpClient.configure(ftpConf);
51  	}
52  	
53  	/**
54  	 * Open an FTP connection to the mainframe. 
55  	 * @param hostname the mainframe IP address
56  	 * @param hostUserID the mainframe user ID used to authenticate
57  	 * @param hostPassword the mainframe password used to authenticate
58  	 * @throws IOException if connection fails
59  	 */
60  	public void open(
61  			final String hostname,
62  			final String hostUserID,
63  			final String hostPassword) throws IOException {
64  		if (_ftpClient.isConnected()) {
65  			_ftpClient.disconnect();
66  		}
67  		_ftpClient.connect(hostname);
68  		if (!FTPReply.isPositiveCompletion(_ftpClient.getReplyCode())) {
69  			throw new IOException(hostname + " not responding");
70  		}
71  		if (!_ftpClient.login(hostUserID, hostPassword)) {
72  			processFtpError();
73  		}
74  	}
75  	
76  	/**
77  	 * Upload a single file to the mainframe.
78  	 * @param remote the z/OS name of the file
79  	 * @param local the local file
80  	 * @throws IOException if upload fails
81  	 */
82  	public void upload(final String remote, final File local) throws IOException {
83  		if (!_ftpClient.sendSiteCommand("FILEtype=SEQ")) {
84  			processFtpError();
85  		}
86  		if (!_ftpClient.storeFile(remote, new FileInputStream(local))) {
87  			processFtpError();
88  		}
89  	}
90  	
91  	/**
92  	 * Submits the job passed as a string.
93  	 * <p/>
94       * Upon return the job is queued in JES. It is possible to query
95       * the status of the job.
96  	 * @param jcl a string containing JCL to submit
97  	 * @return the JES job ID that was assigned
98  	 * @throws IOException if submit fails
99  	 */
100 	public String submitJob(final String jcl) throws IOException {
101 		
102 		String jobId = null;
103 		if (!_ftpClient.sendSiteCommand("FILEtype=JES")) {
104 			processFtpError();
105 		}
106 		
107         OutputStream os = _ftpClient.storeFileStream("P390JCL8");
108         if (os == null) {
109         	processFtpError();
110         }
111         os.write(jcl.getBytes());
112         os.close();
113         if (!_ftpClient.completePendingCommand()) {
114         	processFtpError();
115         }
116         
117         String[] replies = _ftpClient.getReplyStrings();
118         if (replies == null || replies.length == 0) {
119         	processFtpError();
120         }
121         
122         if (replies[0].startsWith(SUBMIT_REPLY)) {
123         	jobId = replies[0].substring(SUBMIT_REPLY.length());
124         } else {
125         	processFtpError();
126         }
127 		
128 		return jobId;
129 	}
130 	
131 	/**
132 	 * Retrieves the output of a job.
133 	 * @param jobId the job ID to retrieve
134 	 * @return the content of the job output files
135 	 * @throws IOException if something goes wrong
136 	 */
137 	public String getJobOutput(final String jobId) throws IOException {
138 		return getJesResource(jobId + ".x");
139 	}
140 	
141 	/**
142 	 * Assuming a JCL is available on the mainframe ready for submission,
143 	 * this will submit that JCL and wait until a result is available.
144 	 * @param  remoteFile the file on the server that holds the JCL
145 	 * @return the content of the submitted job output files
146 	 * @throws IOException if something goes wrong
147 	 */
148 	public String submitWaitForOutput(final String remoteFile) throws IOException {
149 		return getJesResource(remoteFile);
150 	}
151 
152 	/**
153 	 * Generic request to get something back from Jes.
154 	 * @param  a job id or file name holding JCL to submit
155 	 * @return the content of the submitted job output files
156 	 * @throws IOException if something goes wrong
157 	 */
158 	public String getJesResource(final String jesResource) throws IOException {
159 		if (!_ftpClient.sendSiteCommand("FILEtype=JES")) {
160 			processFtpError();
161 		}
162 		ByteArrayOutputStream baos = new ByteArrayOutputStream();
163 		if (!_ftpClient.retrieveFile(jesResource, baos)) {
164 			processFtpError();
165 		}
166 		baos.close();
167 		String result = baos.toString("UTF-8"); // TODO why UTF-8?
168 		return result;
169 	}
170 	
171 	/**
172 	 * Extracts the highest condition code from a job output.
173 	 * @param heldOutput the job held output
174 	 * @return the highest condition code
175 	 */
176 	public int getHighestCondCode(final String heldOutput) {
177 		int maxCondCode = -1;
178 		Matcher matcher = COND_CODE_PATTERN.matcher(heldOutput);
179 		while(matcher.find()) {
180 			int condCode = Integer.parseInt(matcher.group(1));
181 			maxCondCode = (condCode > maxCondCode) ? condCode : maxCondCode;
182 		}
183 		return maxCondCode;
184 	}
185 
186 	/**
187 	 * Close an FTP connection to the mainframe.
188 	 * <p/>
189 	 * Not a real problem if we don't, the mainframe never keeps a connection
190 	 * around for very long anyway.
191 	 * @throws IOException if close fails
192 	 */
193 	public void close() throws IOException {
194 		if (_ftpClient.isConnected()) {
195 			_ftpClient.logout();
196 			_ftpClient.disconnect();
197 		}
198 	}
199 
200 	/**
201 	 * Turns all FTP errors to IO exceptions.
202 	 * @throws IOException systematic
203 	 */
204 	protected void processFtpError() throws IOException {
205 		String errors[] = _ftpClient.getReplyStrings();
206 		_ftpClient.disconnect();
207 		
208 		if (errors == null || errors.length == 0) {
209 			throw new IOException("Unknown error.");
210 		}
211 		throw new IOException(errors[0]);
212 	}
213 }