001/* 002 * Copyright (c) 2012 The Regents of the University of California. 003 * All rights reserved. 004 * 005 * '$Author: crawl $' 006 * '$Date: 2012-05-09 11:05:40 -0700 (Wed, 09 May 2012) $' 007 * '$Revision: 29823 $' 008 * 009 * Permission is hereby granted, without written agreement and without 010 * license or royalty fees, to use, copy, modify, and distribute this 011 * software and its documentation for any purpose, provided that the above 012 * copyright notice and the following two paragraphs appear in all copies 013 * of this software. 014 * 015 * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY 016 * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES 017 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF 018 * THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF 019 * SUCH DAMAGE. 020 * 021 * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, 022 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 023 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE 024 * PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF 025 * CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, 026 * ENHANCEMENTS, OR MODIFICATIONS. 027 * 028 */ 029 030package org.sdm.spa.actors.transport; 031 032import java.io.ByteArrayOutputStream; 033import java.io.File; 034 035import org.apache.commons.logging.Log; 036import org.apache.commons.logging.LogFactory; 037import org.kepler.ssh.ExecException; 038import org.kepler.ssh.LocalExec; 039import org.kepler.ssh.SshExec; 040import org.sdm.spa.actors.transport.vo.ConnectionDetails; 041 042import ptolemy.kernel.util.IllegalActionException; 043 044/** 045 * This class provides functionality to copy files between two machine using 046 * SSH. Remote machines should be accessible using SSH to be able to copy files. 047 * This is a base class that can be extended by Protocol specific class that 048 * generate different commands to perform the copy operation. 049 * <p> 050 * Whether the copy operation would by default overwrite existing files depends 051 * on the actual protocol used for file copy. Optional command line options may 052 * be used to override the default behavior in some cases depending on the 053 * protocol used. Refer to the documentation of specific sub class for details. 054 * <p> 055 * When both source and destination machines are local hosts, the protocol 056 * specified is ignored and file copy is done using Java. 057 * <p> 058 * When both source and destination are the same machine a simple cp command is 059 * used instead of any specific protocol 060 * @author Chandrika Sivaramakrishnan 061 * 062 */ 063public abstract class FileCopierBase { 064 /////////////////Private Variables/////////////////////// 065 private static final Log log = LogFactory.getLog(FileCopierBase.class.getName()); 066 private static final boolean isDebugging = log.isDebugEnabled(); 067 068 /////////////////Protected variables /////////////////// 069 protected boolean forcedCleanup = false; 070 protected int timeout = 0; 071 protected String cmdLineOptions = ""; 072 protected String protocolPathSrc = ""; 073 protected String protocolPathDest = ""; 074 075 //////////////////Protected Methods////////////////// 076 /** 077 * Copies files to destination path on the same machine. 078 * @param srcFile - source file to be copied 079 * @param destFile - local path into which source should be copied 080 * @param recursive - flag to indicate if directories should be copied recursively 081 * @return CopyResult object containing the exit code and error message if any. 082 * exit code 0 represents successful file transfer. 083 */ 084 protected CopyResult copyLocal(String sourceFile, String destinationFile, 085 boolean recursive){ 086 try { 087 int count = 0; 088 LocalExec exObject = new LocalExec(); 089 File file = new File(sourceFile); 090 count = exObject.copyTo(file, destinationFile, recursive); 091 if(count>0){ 092 return new CopyResult(); 093 }else{ 094 return new CopyResult(1,"No files where copied",""); 095 } 096 }catch(ExecException e){ 097 return new CopyResult(1,e.getMessage(),""); 098 } 099 } 100 101 /** 102 * Connects to the remote machines and execute a 'cp' command to copy files 103 * to destination dir on the same remote machine 104 * @param srcFile - source file to be copied 105 * @param destFile - local path into which source should be copied 106 * @param recursive - flag to indicate if directories should be copied recursively 107 * @return CopyResult object containing the exit code and error message if any. 108 * exit code 0 represents successful file transfer. 109 */ 110 protected CopyResult copyLocalOnRemoteMachine(ConnectionDetails srcDetails, 111 String srcFile, ConnectionDetails destDetails, String destFile, 112 boolean recursive) throws ExecException { 113 114 ByteArrayOutputStream cmdStdout = new ByteArrayOutputStream(); 115 ByteArrayOutputStream cmdStderr = new ByteArrayOutputStream(); 116 // Conenct to source by ssh 117 SshExec sshObject = new SshExec(srcDetails.getUser(), srcDetails.getHost(), 118 srcDetails.getPort()); 119 sshObject.setTimeout(timeout, false, false); 120 sshObject.setForcedCleanUp(forcedCleanup); 121 122 try 123 { 124 //no pseudo terminal is required as password is not required for cp 125 sshObject.setPseudoTerminal(false); 126 StringBuffer cmd = new StringBuffer(100); 127 int exitCode = 0; 128 129 cmd.append("cp "); 130 if (recursive) { 131 cmd.append(" -r "); 132 } else { 133 cmd.append(" "); 134 } 135 cmd.append(srcFile); 136 cmd.append(" "); 137 cmd.append(destFile); 138 139 if (isDebugging) 140 log.debug("remote copy cmd=" + cmd); 141 exitCode = sshObject.executeCmd(cmd.toString(), cmdStdout, cmdStderr, 142 destDetails.toString()); 143 log.debug("ExitCode:"+ exitCode); 144 log.debug("stdout:"+cmdStdout); 145 log.debug("stderr:"+cmdStderr); 146 147 String message = cmdStderr.toString(); 148 if(message==null || message.trim().equals("")){ 149 message = cmdStdout.toString(); 150 } 151 return new CopyResult(exitCode,message,null); 152 }catch(Exception e){ 153 return new CopyResult(1, e.getMessage(),null); 154 } 155 } 156 157 /** 158 * Copies file from a remote host to local machine 159 * 160 * @param srcDetails - object containing the source machine connection details 161 * @param srcFile - source file to be copied 162 * @param destFile - local path into which source should be copied 163 * @param recursive - flag to indicate if directories should be copied recursively 164 * @return CopyResult object containing the exit code and error message if any. 165 * exit code 0 represents successful file transfer. 166 * @throws ExecException 167 */ 168 protected abstract CopyResult copyFrom(ConnectionDetails srcDetails, String srcFile, 169 String destFile, boolean recursive) throws ExecException; 170 171 /** 172 * Copies files from local machine to a remote host 173 * 174 * @param srcFile - local source file to be copied 175 * @param destDetails - object containing the destination machine connection details 176 * @param destFile - path into which source should be copied 177 * @param recursive - flag to indicate if directories should be copied recursively 178 * @return CopyResult object containing the exit code and error message if any. 179 * exit code 0 represents successful file transfer. 180 * @throws ExecException 181 */ 182 protected abstract CopyResult copyTo(String srcFile, ConnectionDetails destDetails, 183 String destFile, boolean recursive) throws ExecException; 184 185 /** 186 * Copies files between two remote machines. 187 * 188 * @param srcDetails - object containing the source machine connection details 189 * @param srcFile - source file to be copied 190 * @param destDetails - object containing the destination machine connection details 191 * @param destFile - path into which source should be copied 192 * @param recursive - flag to indicate if directory should be copied recursively 193 * @return CopyResult object containing the exit code and error message if any. 194 * exit code 0 represents successful file transfer. 195 * @throws ExecException 196 */ 197 protected abstract CopyResult copyRemote(ConnectionDetails srcDetails, 198 String srcFile, ConnectionDetails destDetails, String destFile, 199 boolean recursive) throws ExecException; 200 201 /** 202 * Generic copy method that does the initial input validation and calls the 203 * copyTo, copyFrom or copyRemote of the appropriate FileCopier subclass. 204 * Subclasses of FileCopier implement these methods based on the protocol that 205 * it uses for file copy. If both source and destination are local host, 206 * ignores the protocol specified by the user and copies file using java 207 * <p> 208 * @param srcDetails - ConnectionDetails object with source machine details 209 * @param srcFile - File to be copied 210 * @param destDetails - ConnectionDetails object with destination machine details 211 * @param destFile - Destination file or directory 212 * @param recursive - whether directory should be copied recursively 213 * @return exitCode 214 * @throws IllegalActionException 215 * @throws ExecException 216 */ 217 protected CopyResult copy(ConnectionDetails srcDetails, String srcFile, 218 ConnectionDetails destDetails, String destFile, boolean recursive) 219 throws IllegalActionException, ExecException { 220 221 if (srcDetails.isLocal()) { 222 srcFile = handleRelativePath(srcFile); 223 } 224 225 if (destDetails.isLocal()) { 226 destFile = handleRelativePath(destFile); 227 } 228 229 if (srcDetails.getPort() == -1) { 230 srcDetails.setPort(getDefaultPort()); 231 } 232 if (destDetails.getPort() == -1) { 233 destDetails.setPort(getDefaultPort()); 234 } 235 236 if (isDebugging) { 237 log.debug("Source= " + srcDetails); 238 log.debug("Destination= " + destDetails); 239 } 240 241 //Both source and destination are local hosts 242 if (srcDetails.isLocal() && destDetails.isLocal()) { 243 return copyLocal(srcFile, destFile, recursive); 244 } 245 246 //Either is a local host 247 if (srcDetails.isLocal()) { 248 // copy to remote destination 249 return copyTo(srcFile, destDetails, destFile, recursive); 250 } else if (destDetails.isLocal()) { 251 return copyFrom(srcDetails, srcFile, destFile, recursive); 252 } 253 254 //Check if both the src and destination remote machines are same 255 if(srcDetails.getHost().equals(destDetails.getHost())){ 256 return copyLocalOnRemoteMachine(srcDetails, srcFile, destDetails, destFile, recursive); 257 } 258 return copyRemote(srcDetails, srcFile, destDetails, destFile, recursive); 259 } 260 261 262 263/** 264 * This is used to set the users PATH variable, if the user has not specified 265 * the path where the protocol is installed. In such cases the 266 * program will search for it in a default list of path. 267 * @param cmd - command to execute for file copy 268 * @return original command prefixed with command to set PATH variable. 269 */ 270 protected String getCmdWithDefaultPath(StringBuffer cmd) { 271 StringBuffer cmdWithPath = new StringBuffer(100); 272 cmdWithPath 273 .append("bash -c 'export PATH=/usr/bin:/bin:/usr/local/bin:~:.:$PATH; "); 274 cmdWithPath.append(cmd); 275 cmdWithPath.append("'"); 276 return cmdWithPath.toString(); 277 } 278 279 /** 280 * default port for the file transfer protocol. Child class should either 281 * return a specific port number or -1 if it doesn't want to enforce a 282 * specific port number 283 * */ 284 protected abstract int getDefaultPort(); 285 286 //////////////////Private methods /////////////////////////////////// 287 288private String handleRelativePath(String localfile){ 289 String fileWithPath = localfile.trim(); 290 291 String userhome = System.getProperty("user.home"); 292 //Work around for java 1.6 bug. Java doesn't return the correct 293 //user home directory on vista or windows 7. 294 //http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6519127 295 if ( System.getProperty("os.name").toLowerCase().indexOf("win") >= 0 ) { 296 userhome = System.getenv().get("HOMEPATH"); 297 } 298 299 if (localfile.contains(",")) { 300 log.debug("***** Detected file list *******"); 301 String[] srcFile_list = localfile.split(","); 302 StringBuffer newlist = new StringBuffer(); 303 for (int i = 0; i < srcFile_list.length; i++) { 304 srcFile_list[i] = srcFile_list[i].trim(); 305 // Anand: The first predicate applies to Windows; the 306 // selected offset assumes that 307 // drive identifiers will be 1 character long. 308 if (!(srcFile_list[i].startsWith(":\\", 1) || 309 srcFile_list[i].startsWith(":/", 1) || 310 srcFile_list[i].startsWith("/"))) { 311 newlist.append(userhome); 312 newlist.append(File.separatorChar); 313 } 314 newlist.append(srcFile_list[i]); 315 newlist.append(","); 316 } 317 fileWithPath = newlist.toString(); 318 if(fileWithPath.endsWith(",")){ 319 fileWithPath = fileWithPath.substring(0, fileWithPath.length() -1); 320 } 321 322 } else { 323 // single file 324 // The first predicate applies to Windows; the selected offset 325 // assumes that 326 // drive identifiers will be 1 character long. 327 // We can access file using relative path 328 if (!(fileWithPath.startsWith(":\\", 1) 329 || fileWithPath.startsWith(":/", 1) 330 || fileWithPath.startsWith("/"))) { 331 fileWithPath = userhome + File.separatorChar + fileWithPath; 332 } 333 } 334 335 log.debug("From handleRelativePath - Returning "+fileWithPath); 336 return fileWithPath; 337} 338 339 /////////////////Public getters and setters///////////////////////// 340 public boolean isForcedCleanup() { 341 return forcedCleanup; 342 } 343 344 public void setCleanup(boolean cleanup) { 345 this.forcedCleanup = cleanup; 346 } 347 348 public int getTimeout() { 349 return timeout; 350 } 351 352 public void setTimeout(int timeout) { 353 this.timeout = timeout; 354 } 355 356 public String getCmdLineOptions() { 357 return cmdLineOptions; 358 } 359 360 public void setCmdLineOptions(String cmdLineOptions) { 361 if (cmdLineOptions == null) { 362 this.cmdLineOptions = ""; 363 } else { 364 this.cmdLineOptions = cmdLineOptions.trim(); 365 } 366 } 367 368 public String getProtocolPathSrc() { 369 return protocolPathSrc; 370 } 371 372 public void setProtocolPathSrc(String protocolPathSrc) { 373 if (protocolPathSrc == null || protocolPathSrc.trim().equals("")) { 374 this.protocolPathSrc = ""; 375 } else { 376 protocolPathSrc = protocolPathSrc.trim(); 377 String seperator = "/"; 378 if (protocolPathSrc.contains("\\")) { 379 seperator = "\\"; 380 } 381 if (protocolPathSrc.endsWith(seperator)) { 382 this.protocolPathSrc = protocolPathSrc; 383 } else { 384 this.protocolPathSrc = protocolPathSrc + seperator; 385 } 386 } 387 } 388 389 public String getProtocolPathDest() { 390 return protocolPathDest; 391 } 392 393 public void setProtocolPathDest(String protocolPathDest) { 394 if (protocolPathDest == null || protocolPathDest.trim().equals("")) { 395 this.protocolPathDest = ""; 396 } else { 397 String seperator = "/"; 398 if (protocolPathDest.contains("\\")) { 399 seperator = "\\"; 400 } 401 if (protocolPathDest.endsWith(seperator)) { 402 this.protocolPathDest = protocolPathDest; 403 } else { 404 this.protocolPathDest = protocolPathDest + seperator; 405 } 406 } 407 } 408 409 //Inner class 410 /**Object that contains the exit code and (error) message associated with the 411 *copy operation. Expects a exit code of 0 to denote successful file transfer. 412 *If exit code is zero, the error message is set to empty string 413 */ 414 public class CopyResult { 415 private int exitCode; 416 private String errorMsg; 417 private String warningMsg; 418 419 /** 420 * Default constructor. Represents a successful file transfer. 421 * Defaults exit code to zero and message to empty string 422 */ 423 public CopyResult(){ 424 exitCode = 0; 425 errorMsg = ""; 426 warningMsg = ""; 427 } 428 429 public CopyResult(int exitCode, String message, String warningMessage){ 430 this.exitCode = exitCode; 431 if(warningMessage == null){ 432 warningMessage = ""; 433 } 434 if(exitCode==0){ 435 //operation successful 436 this.errorMsg =""; 437 this.warningMsg = warningMessage; 438 }else{ 439 this.errorMsg = message; 440 this.warningMsg = warningMessage; 441 } 442 } 443 444 @Override 445 public String toString(){ 446 return exitCode+":"+errorMsg + ", warnings:" + warningMsg; 447 } 448 449 public int getExitCode() { 450 return exitCode; 451 } 452 453 public void setExitCode(int exitCode) { 454 this.exitCode = exitCode; 455 } 456 457 public String getErrorMsg() { 458 return errorMsg; 459 } 460 461 public void setErrorMsg(String errorMsg) { 462 this.errorMsg = errorMsg; 463 } 464 465 public String getWarningMsg() { 466 return warningMsg; 467 } 468 469 public void setWarningMsg(String warningMsg) { 470 this.warningMsg = warningMsg; 471 } 472 } 473 474}