001/* 002 * Copyright (c) 2004-2010 The Regents of the University of California. 003 * All rights reserved. 004 * 005 * '$Author: welker $' 006 * '$Date: 2010-05-05 22:21:26 -0700 (Wed, 05 May 2010) $' 007 * '$Revision: 24234 $' 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.kepler.actor.ssh; 031 032import java.io.File; 033 034import org.apache.commons.logging.Log; 035import org.apache.commons.logging.LogFactory; 036import org.kepler.ssh.ExecException; 037import org.kepler.ssh.ExecFactory; 038import org.kepler.ssh.ExecInterface; 039import org.kepler.ssh.LocalExec; 040 041import ptolemy.actor.TypedAtomicActor; 042import ptolemy.actor.TypedIOPort; 043import ptolemy.actor.parameters.PortParameter; 044import ptolemy.data.BooleanToken; 045import ptolemy.data.StringToken; 046import ptolemy.data.expr.Parameter; 047import ptolemy.data.type.BaseType; 048import ptolemy.kernel.CompositeEntity; 049import ptolemy.kernel.util.IllegalActionException; 050import ptolemy.kernel.util.NameDuplicationException; 051 052////////////////////////////////////////////////////////////////////////// 053//// FileCopier 054/** 055 * Connects to a remote host using Ssh protocol (or does nothing for the local 056 * host) and copies a file to or from there. 057 * 058 * <p> 059 * This actor uses the org.kepler.ssh package for longlasting connections 060 * 061 * <p> 062 * The file references should be in the format: [[user@]host:]path. E.g. 063 * <ul> 064 * <li><it>foo.txt</it> foo.txt in the current dir on the local machine</li> 065 * <li><it>playdir/foo.txt</it> relative path to current dir on the local 066 * machine</li> 067 * <li><it>/home/littleboy/playdir/foo.txt<it> absolute path on the local 068 * machine</li> 069 * <li><it>local:playdir/foo.txt</it> relative path to $HOME on the local 070 * machine</li> 071 * <li><it>localhost:playdir/foo.txt</it> relative path to $HOME on the 072 * 'localhost' machine (it counts to be a remote file!)</li> 073 * <li><it>john@farmachine:playdir/foo.txt</it> relative path to $HOME on the 074 * 'farmachine' machine of user 'john'</li> 075 * </ul> 076 * 077 * <p> 078 * The target becomes overwritten if exists, just like with scp and cp, in case 079 * of single files. For directories, a subdirectory will be created with the 080 * name of the source within the existing directory (again, just like with scp 081 * and cp). 082 * 083 * <p> 084 * If the source refers to a directory, you should set the parameter 'recursive' 085 * to true, and then the whole directory will be copied to target. 086 * 087 * <p> 088 * Either the source or the target file should be local. This actor cannot copy 089 * remote files to remote places. For such operations, you need to use ExecCmd 090 * actor with executing remote scp commands. 091 * 092 * <p> 093 * If both source and target refers to local files/directories, the Java File 094 * class will be used for local copy instead of ssh. It behaves similarly to 095 * other local file copier actors of Kepler but you do not need to change your 096 * workflow for remote/local executions by using this actor. 097 * 098 * <p> 099 * This actor produces a Boolean token on 'succ' port. TRUE indicates successful 100 * operation, while false indicates an error. The actor also produces a String 101 * token on the 'error' port; an empty string on success, internal error 102 * messages on failure. 103 * 104 * @author Norbert Podhorszki 105 * @version $Revision: 24234 $ 106 * @category.name remote 107 * @category.name connection 108 * @category.name file operation 109 */ 110 111public class FileCopier extends TypedAtomicActor { 112 113 /** 114 * Construct an ExecuteCmd actor with the given container and name. Create 115 * the parameters, initialize their values. 116 * 117 * @param container 118 * The container. 119 * @param name 120 * The name of this actor. 121 * @exception IllegalActionException 122 * If the entity cannot be contained by the proposed 123 * container. 124 * @exception NameDuplicationException 125 * If the container already has an actor with this name. 126 */ 127 public FileCopier(CompositeEntity container, String name) 128 throws NameDuplicationException, IllegalActionException { 129 130 super(container, name); 131 132 /* 133 * Input ports 134 */ 135 136 // source file/dir 137 source = new PortParameter(this, "source", new StringToken( 138 "[[user]@host:]path")); 139 new Parameter(source.getPort(), "_showName", BooleanToken.TRUE); 140 141 // target file/dir 142 target = new PortParameter(this, "target", new StringToken( 143 "[[user]@host:]path")); 144 new Parameter(target.getPort(), "_showName", BooleanToken.TRUE); 145 146 // recursive parameter 147 recursive = new Parameter(this, "recursive", new BooleanToken(false)); 148 recursive.setTypeEquals(BaseType.BOOLEAN); 149 150 /* 151 * Output ports 152 */ 153 154 succ = new TypedIOPort(this, "succ", false, true); 155 succ.setTypeEquals(BaseType.BOOLEAN); 156 new Parameter(succ, "_showName", BooleanToken.TRUE); 157 158 error = new TypedIOPort(this, "error", false, true); 159 error.setTypeEquals(BaseType.STRING); 160 new Parameter(error, "_showName", BooleanToken.TRUE); 161 162 _attachText("_iconDescription", "<svg>\n" + "<rect x=\"0\" y=\"0\" " 163 + "width=\"75\" height=\"50\" style=\"fill:blue\"/>\n" 164 + "<text x=\"5\" y=\"30\"" 165 + "style=\"font-size:14; fill:yellow; font-family:SansSerif\">" 166 + "SshExec</text>\n" + "</svg>\n"); 167 } 168 169 // //////////////// Public ports and parameters /////////////////////// 170 171 /** 172 * Source in user@host:path format. If user is not provided, the local 173 * username will be used. If host is "local" or empty string, the path is 174 * handled as local path. 175 */ 176 public PortParameter source; 177 178 /** 179 * Target in user@host:path format. If user is not provided, the local 180 * username will be used. If host is "local:" or empty string, the path is 181 * handled as local path. 182 */ 183 public PortParameter target; 184 185 /** 186 * The flag of successful copy. It is a port of type Boolean token. 187 */ 188 public TypedIOPort succ; 189 190 /** 191 * The string representation of all the errors that happened during the 192 * execution of the actor, if there are any. A port of type String token. 193 */ 194 public TypedIOPort error; 195 196 /** 197 * Specifying whether directories can be copied recursively. 198 */ 199 public Parameter recursive; 200 201 // ///////////////////////////////////////////////////////////////// 202 // // public methods //// 203 204 /** 205 * Perform copying. 206 * 207 * @exception IllegalActionException 208 * If it is thrown by the send() method sending out the 209 * token. 210 */ 211 public void fire() throws IllegalActionException { 212 super.fire(); 213 214 // get inputs 215 source.update(); 216 String strSource = ((StringToken) source.getToken()).stringValue() 217 .trim(); 218 219 target.update(); 220 String strTarget = ((StringToken) target.getToken()).stringValue() 221 .trim(); 222 223 boolean recursiveFlag = ((BooleanToken) recursive.getToken()) 224 .booleanValue(); 225 226 /* 227 * process source string 228 */ 229 String userSource; 230 String hostSource; 231 //int portSource = 22; 232 int portSource = -1; 233 String pathSource; 234 boolean relativeToCurrentSource = false; // true: local file, relative 235 // to current dir 236 237 if (strSource.indexOf(":\\") == -1) { // not windows path like D:\Temp 238 239 // get USER 240 int atPos = strSource.indexOf('@'); 241 if (atPos >= 0) 242 userSource = strSource.substring(0, atPos); 243 else 244 userSource = System.getProperty("user.name"); 245 246 // get HOST 247 int colonPos = strSource.indexOf(':'); 248 if (colonPos >= 0) 249 if (atPos >= 0) 250 hostSource = strSource.substring(atPos + 1, colonPos); 251 else 252 hostSource = strSource.substring(0, colonPos); 253 else { 254 hostSource = new String("local"); 255 relativeToCurrentSource = true; 256 } 257 258 // get PORT (default 22 is already set) 259 String subS = strSource.substring(colonPos + 1); 260 if (colonPos >= 0) { 261 colonPos = subS.indexOf(':'); // look for second occurence of : 262 if (colonPos >= 0) { 263 String portStr = subS.substring(0, colonPos); 264 if (!portStr.trim().equals("")) { 265 try { 266 portSource = Integer.parseInt(portStr); 267 } catch (java.lang.NumberFormatException ex) { 268 throw new IllegalActionException( 269 "The port should be a number or omitted in source path " 270 + strSource); 271 } 272 } 273 } 274 } 275 pathSource = subS.substring(colonPos + 1); // the rest of the string 276 277 } else { // windows path means local path 278 userSource = System.getProperty("user.name"); 279 hostSource = new String("local"); 280 pathSource = strSource; 281 relativeToCurrentSource = true; 282 } 283 284 if (isDebugging) 285 log 286 .debug("Source: user=[" + userSource + "], host=[" 287 + hostSource + "], port=[" + portSource 288 + "], path=[" + pathSource + "]"); 289 290 /* 291 * process target string 292 */ 293 String userTarget; 294 String hostTarget; 295 //int portTarget = 22; 296 int portTarget = -1; 297 String pathTarget; 298 boolean relativeToCurrentTarget = false; // true: local file, relative 299 // to current dir 300 301 if (strTarget.indexOf(":\\") == -1) { // not windows path like D:\Temp 302 303 // get USER 304 int atPos = strTarget.indexOf('@'); 305 if (atPos >= 0) 306 userTarget = strTarget.substring(0, atPos); 307 else 308 userTarget = System.getProperty("user.name"); 309 310 // get HOST 311 int colonPos = strTarget.indexOf(':'); 312 if (colonPos >= 0) 313 if (atPos >= 0) 314 hostTarget = strTarget.substring(atPos + 1, colonPos); 315 else 316 hostTarget = strTarget.substring(0, colonPos); 317 else { 318 hostTarget = new String("local"); 319 relativeToCurrentTarget = true; 320 } 321 322 // get PORT (default 22 is already set) 323 String subS = strTarget.substring(colonPos + 1); 324 if (colonPos >= 0) { 325 colonPos = subS.indexOf(':'); // look for second occurence of : 326 if (colonPos >= 0) { 327 String portStr = subS.substring(0, colonPos); 328 if (!portStr.trim().equals("")) { 329 try { 330 portTarget = Integer.parseInt(portStr); 331 } catch (java.lang.NumberFormatException ex) { 332 throw new IllegalActionException( 333 "The port should be a number or omitted in target path " 334 + strTarget); 335 } 336 } 337 } 338 } 339 pathTarget = subS.substring(colonPos + 1); // the rest of the string 340 341 } else { // windows path means local path 342 userTarget = System.getProperty("user.name"); 343 hostTarget = new String("local"); 344 pathTarget = strTarget; 345 relativeToCurrentTarget = true; 346 } 347 348 if (isDebugging) 349 log 350 .debug("Target: user=[" + userTarget + "], host=[" 351 + hostTarget + "], port=[" + portTarget 352 + "], path=[" + pathTarget + "]"); 353 354 // error check: not both refers to remote place 355 if (!hostSource.equals("local") && !hostTarget.equals("local")) { 356 String msg = new String( 357 "One of the source and target should be local."); 358 log.error(msg); 359 succ.send(0, new BooleanToken(false)); 360 error.send(0, new StringToken(msg)); 361 return; 362 } 363 364 // error check: source reference is empty 365 if (pathSource.length() == 0) { 366 String msg = new String("Source path is empty in " + strSource); 367 log.error(msg); 368 succ.send(0, new BooleanToken(false)); 369 error.send(0, new StringToken(msg)); 370 return; 371 } 372 373 // semantic check: target reference is empty: replace with . 374 // SshExec's copyTo does not work with empty path string but it works 375 // the same 376 // on . (scp works the same on empty string and .) 377 if (pathTarget.length() == 0) { 378 pathTarget = new String("."); 379 } 380 381 // source case of local:relativePath --> relative to $HOME, so 382 // absolutize now 383 if (hostSource.equals("local") && !relativeToCurrentSource 384 && pathSource.charAt(0) != File.separatorChar) { 385 386 pathSource = System.getProperty("user.home") + File.separator 387 + pathSource; 388 if (isDebugging) 389 log.debug("Source path absolutized to $HOME: " + pathSource); 390 } 391 392 // target case of local:relativePath --> relative to $HOME, so 393 // absolutize now 394 if (hostTarget.equals("local") && !relativeToCurrentTarget 395 && pathTarget.charAt(0) != File.separatorChar) { 396 397 pathTarget = System.getProperty("user.home") + File.separator 398 + pathTarget; 399 if (isDebugging) 400 log.debug("Target path absolutized to $HOME: " + pathTarget); 401 } 402 403 // select the appropriate execution setting for the current source and 404 // target 405 ExecInterface execObj; // local or remote copy 406 boolean copyTo = true; // copyTo (local->remote) or copyFrom 407 // (remote->local) 408 try{ 409 if (hostSource.equals("local") && hostTarget.equals("local")) { 410 // local copy 411 if (isDebugging) 412 log.debug("Execution mode: local using Java File class"); 413 execObj = new LocalExec(); 414 copyTo = true; 415 } else if (hostSource.equals("local")) { 416 // create an SshExec object, and set copyTo to true 417 if (isDebugging) 418 log.debug("Execution mode: remote copyTo using ssh"); 419 //execObj = new SshExec(userTarget, hostTarget, portTarget); 420 execObj = ExecFactory.getExecObject(userTarget, hostTarget, portTarget); 421 copyTo = true; 422 } else { 423 // create an SshExec object, and set copyTo to false 424 if (isDebugging) 425 log.debug("Execution mode: remote copyFrom using ssh"); 426 //execObj = new SshExec(userSource, hostSource, portSource); 427 execObj = ExecFactory.getExecObject(userSource, hostSource, portSource); 428 copyTo = false; 429 } 430 }catch(ExecException e){ 431 String errText = new String("Error connecting to remote host:\n" 432 + e.getMessage()); 433 434 log.error(errText); 435 succ.send(0, new BooleanToken(false)); 436 error.send(0, new StringToken(errText)); 437 return; 438 } 439 440 File lfile; 441 int numberOfCopiedFiles = 0; 442 443 long startTime = System.currentTimeMillis(); 444 try { 445 if (copyTo) { 446 lfile = new File(pathSource); 447 numberOfCopiedFiles = execObj.copyTo(lfile, pathTarget, 448 recursiveFlag); 449 } else { 450 lfile = new File(pathTarget); 451 numberOfCopiedFiles = execObj.copyFrom(pathSource, lfile, 452 recursiveFlag); 453 } 454 455 } catch (ExecException e) { 456 String errText = new String("Error at copy execution:\n" 457 + e.getMessage()); 458 459 if (isDebugging) 460 log.debug(errText); 461 succ.send(0, new BooleanToken(false)); 462 error.send(0, new StringToken(errText)); 463 return; 464 } 465 long endTime = System.currentTimeMillis(); 466 467 if (isDebugging) 468 log.debug("Number of copied files = " + numberOfCopiedFiles 469 + ". Time to copy: " + (endTime - startTime) + " msec."); 470 471 if (numberOfCopiedFiles <= 0) { 472 String errText = new String( 473 "No file(s) were copied for unknown reasons\n"); 474 if (isDebugging) 475 log.debug(errText); 476 succ.send(0, new BooleanToken(false)); 477 error.send(0, new StringToken(errText)); 478 } else { 479 // finally, good news can be reported 480 succ.send(0, new BooleanToken(true)); 481 error.send(0, new StringToken("")); 482 } 483 484 } // end-method fire() 485 486 private static final Log log = LogFactory 487 .getLog(FileCopier.class.getName()); 488 private static final boolean isDebugging = log.isDebugEnabled(); 489} 490 491// vim: sw=4 ts=4 et