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-06 05:21:26 +0000 (Thu, 06 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 org.apache.commons.logging.Log;
033import org.apache.commons.logging.LogFactory;
034import org.kepler.ssh.ExecException;
035import org.kepler.ssh.ExecFactory;
036import org.kepler.ssh.ExecInterface;
037
038import ptolemy.actor.TypedAtomicActor;
039import ptolemy.actor.TypedIOPort;
040import ptolemy.actor.parameters.PortParameter;
041import ptolemy.data.BooleanToken;
042import ptolemy.data.StringToken;
043import ptolemy.data.expr.Parameter;
044import ptolemy.data.type.BaseType;
045import ptolemy.kernel.CompositeEntity;
046import ptolemy.kernel.util.IllegalActionException;
047import ptolemy.kernel.util.NameDuplicationException;
048
049//////////////////////////////////////////////////////////////////////////
050//// FileRemover
051/**
052 * <p>
053 * Connects to a remote host using Ssh protocol (or does nothing for the local
054 * host) and deletes files/directories matching a given mask.
055 * 
056 * </p>
057 * <p>
058 * This actor uses the org.kepler.ssh package for longlasting connections. If
059 * the host is empty string or equals "local", the Java Runtime will be used for
060 * local execution instead of ssh.
061 * 
062 * </p>
063 * <p>
064 * The input should define:
065 * <ul>
066 * <li>the target machine, either "" or "local" to denote the local machine to
067 * be used by Java I/O commands, OR "[user@]host[:port]" to denote a remote
068 * machine to be used by an ssh connection.</li>
069 * <li>the file mask to be deleted (given as String).</li>
070 * <li>The flag 'recursive' should be set if you want to delete directories.</li>
071 * <li>The flag 'allowMask' should be set if you want to use wildcards.</li>
072 * </ul>
073 * 
074 * </p>
075 * <p>
076 * A file mask can contain wildcards in the path expressions as well as in the
077 * file name part. E.g. /path/d*r/../sub??/./f*.txt is a valid expression. In
078 * case of remote operations, the command will be actually the 'rm -rf' command
079 * (without -r if 'recursive' is not set).
080 * 
081 * </p>
082 * <p>
083 * A relative path in the file mask is relative to the home directory in case of
084 * remote operations and relative to the current directory in case of local
085 * operations.
086 * 
087 * </p>
088 * <p>
089 * Note, that symbolic links will also be deleted, but if they are referring to
090 * a directory, they are not followed. This is how 'rm -rf' works and the local
091 * version implements the same behaviour.
092 * 
093 * </p>
094 * <p>
095 * This actor produces a Boolean token on 'succ' port. TRUE indicates successful
096 * operation, while false indicates an error. The actor also produces a String
097 * token on the 'error' port; an empty string on success, internal error
098 * messages on failure.
099 * </p>
100 * 
101 * @author Norbert Podhorszki
102 * @version $Revision: 24234 $
103 * @category.name remote
104 * @category.name connection
105 * @category.name file operation
106 */
107
108public class FileRemover extends TypedAtomicActor {
109
110        /**
111         * Construct an FileRemover actor with the given container and name. Create
112         * the parameters, initialize their values.
113         * 
114         * @param container
115         *            The container.
116         * @param name
117         *            The name of this actor.
118         * @exception IllegalActionException
119         *                If the entity cannot be contained by the proposed
120         *                container.
121         * @exception NameDuplicationException
122         *                If the container already has an actor with this name.
123         */
124        public FileRemover(CompositeEntity container, String name)
125                        throws NameDuplicationException, IllegalActionException {
126                super(container, name);
127
128                /*
129                 * Input ports
130                 */
131
132                // target machine
133                target = new PortParameter(this, "target", new StringToken(
134                                "[user@]host[:port]"));
135                new Parameter(target.getPort(), "_showName", BooleanToken.TRUE);
136
137                // file mask
138                mask = new PortParameter(this, "mask", new StringToken("none.txt"));
139                new Parameter(mask.getPort(), "_showName", BooleanToken.TRUE);
140
141                // recursive parameter
142                recursive = new Parameter(this, "recursive", new BooleanToken(false));
143                recursive.setTypeEquals(BaseType.BOOLEAN);
144
145                // allowMask parameter
146                allowMask = new Parameter(this, "allowMask", new BooleanToken(false));
147                allowMask.setTypeEquals(BaseType.BOOLEAN);
148
149                /*
150                 * Output ports
151                 */
152
153                succ = new TypedIOPort(this, "succ", false, true);
154                succ.setTypeEquals(BaseType.BOOLEAN);
155                new Parameter(succ, "_showName", BooleanToken.TRUE);
156
157                error = new TypedIOPort(this, "error", false, true);
158                error.setTypeEquals(BaseType.STRING);
159                new Parameter(error, "_showName", BooleanToken.TRUE);
160
161        }
162
163        // //////////////// Public ports and parameters ///////////////////////
164
165        /**
166         * Target in user@host:port format. If user is not provided, the local
167         * username will be used. If port is not provided, the default port 22 will
168         * be applied. If host is "local" or empty string, the path is handled as
169         * local path.
170         */
171        public PortParameter target;
172
173        /**
174         * File mask as String. Path expressions are allowed.
175         */
176        public PortParameter mask;
177
178        /**
179         * The flag of successful removal. It will be true if ALL matched files and
180         * directories are deleted. If 'recursive' is not set and directories are
181         * also matched, the value will be false. Note, that files will be still
182         * removed in the latter case. It is a port of type Boolean token.
183         */
184        public TypedIOPort succ;
185
186        /**
187         * The string representation of all the errors that happened during the
188         * execution of the actor, if there are any. A port of type String token.
189         */
190        public TypedIOPort error;
191
192        /**
193         * Specifying whether directories can be removed recursively.
194         */
195        public Parameter recursive;
196
197        /**
198         * Specifying whether wildcards (* and ?) are allowed in the mask.
199         */
200        public Parameter allowMask;
201
202        // /////////////////////////////////////////////////////////////////
203        // // public methods ////
204
205        /**
206         * Perform copying.
207         * 
208         * @exception IllegalActionException
209         *                If it is thrown by the send() method sending out the
210         *                token.
211         */
212        public void fire() throws IllegalActionException {
213                super.fire();
214
215                // get inputs
216                target.update();
217                String strTarget = ((StringToken) target.getToken()).stringValue()
218                                .trim();
219
220                mask.update();
221                String strMask = ((StringToken) mask.getToken()).stringValue().trim();
222
223                boolean recursiveFlag = ((BooleanToken) recursive.getToken())
224                                .booleanValue();
225                boolean allowFlag = ((BooleanToken) allowMask.getToken())
226                                .booleanValue();
227
228
229                // execute the file removal command
230                boolean result = false;
231                try {
232                        // select the appropriate execution setting for the current source and
233                        // target
234                        ExecInterface execObj = ExecFactory.getExecObject(strTarget);
235                        result = execObj.deleteFile(strMask, recursiveFlag, allowFlag);
236                
237                } catch (ExecException e) {
238                        String errText = new String("Error at execution:\n"
239                                        + e.getMessage());
240
241                        log.error(errText);
242                        succ.send(0, new BooleanToken(false));
243                        error.send(0, new StringToken(errText));
244                        return;
245                }
246
247                // report
248                if (result) {
249                        // finally, good news can be reported
250                        succ.send(0, new BooleanToken(true));
251                        error.send(0, new StringToken(""));
252                } else {
253                        String errText = new String(
254                                        "Some file(s) were not deleted for unknown reasons on host "
255                                                        + strTarget + " with mask " + strMask
256                                                        + " with flags recursive=" + recursiveFlag
257                                                        + " and allowMask=" + allowFlag);
258                        log.warn(errText);
259                        succ.send(0, new BooleanToken(false));
260                        error.send(0, new StringToken(errText));
261                }
262
263        } // end-method fire()
264
265        private static final Log log = LogFactory.getLog(FileRemover.class
266                        .getName());
267        private static final boolean isDebugging = log.isDebugEnabled();
268}