View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.vfs.tasks;
18  
19  import org.apache.commons.vfs.FileName;
20  import org.apache.commons.vfs.FileObject;
21  import org.apache.commons.vfs.FileType;
22  import org.apache.commons.vfs.NameScope;
23  import org.apache.commons.vfs.Selectors;
24  import org.apache.commons.vfs.util.Messages;
25  import org.apache.tools.ant.BuildException;
26  import org.apache.tools.ant.Project;
27  
28  import java.util.ArrayList;
29  import java.util.HashSet;
30  import java.util.Set;
31  import java.util.StringTokenizer;
32  
33  /***
34   * An abstract file synchronization task.  Scans a set of source files and
35   * folders, and a destination folder, and performs actions on missing and
36   * out-of-date files.  Specifically, performs actions on the following:
37   * <ul>
38   * <li>Missing destination file.
39   * <li>Missing source file.
40   * <li>Out-of-date destination file.
41   * <li>Up-to-date destination file.
42   * </ul>
43   *
44   * @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a>
45   * @version $Revision: 480428 $ $Date: 2006-11-29 07:15:24 +0100 (Mi, 29 Nov 2006) $
46   * @todo Deal with case where dest file maps to a child of one of the source files
47   * @todo Deal with case where dest file already exists and is incorrect type (not file, not a folder)
48   * @todo Use visitors
49   * @todo Add default excludes
50   * @todo Allow selector, mapper, filters, etc to be specified.
51   * @todo Handle source/dest directories as well
52   * @todo Allow selector to be specified for choosing which dest files to sync
53   */
54  public abstract class AbstractSyncTask
55      extends VfsTask
56  {
57      private final ArrayList srcFiles = new ArrayList();
58      private String destFileUrl;
59      private String destDirUrl;
60      private String srcDirUrl;
61      private boolean srcDirIsBase;
62      private boolean failonerror = true;
63      private String filesList;
64  
65      /***
66       * Sets the destination file.
67       */
68      public void setDestFile(final String destFile)
69      {
70          this.destFileUrl = destFile;
71      }
72  
73      /***
74       * Sets the destination directory.
75       */
76      public void setDestDir(final String destDir)
77      {
78          this.destDirUrl = destDir;
79      }
80  
81      /***
82       * Sets the source file
83       */
84      public void setSrc(final String srcFile)
85      {
86          final SourceInfo src = new SourceInfo();
87          src.setFile(srcFile);
88          addConfiguredSrc(src);
89      }
90  
91      /***
92       * Sets the source directory
93       */
94      public void setSrcDir(final String srcDir)
95      {
96          this.srcDirUrl = srcDir;
97      }
98  
99      /***
100      * Sets whether the source directory should be consider as the base directory.
101      */
102     public void setSrcDirIsBase(final boolean srcDirIsBase)
103     {
104         this.srcDirIsBase = srcDirIsBase;
105     }
106 
107     /***
108      * Sets whether we should fail if there was an error or not
109      */
110     public void setFailonerror(final boolean failonerror)
111     {
112         this.failonerror = failonerror;
113     }
114     
115     /***
116      * Sets whether we should fail if there was an error or not
117      */
118     public boolean isFailonerror()
119     {
120         return failonerror;
121     }
122     
123     /***
124      * Sets the files to includes
125      */
126     public void setIncludes(final String filesList)
127     {
128         this.filesList = filesList;
129     }
130 
131     /***
132      * Adds a nested <src> element.
133      */
134     public void addConfiguredSrc(final SourceInfo srcInfo)
135         throws BuildException
136     {
137         if (srcInfo.file == null)
138         {
139             final String message = Messages.getString("vfs.tasks/sync.no-source-file.error");
140             throw new BuildException(message);
141         }
142         srcFiles.add(srcInfo);
143     }
144 
145     /***
146      * Executes this task.
147      */
148     public void execute() throws BuildException
149     {
150         // Validate
151         if (destFileUrl == null && destDirUrl == null)
152         {
153             final String message =
154                 Messages.getString("vfs.tasks/sync.no-destination.error");
155             logOrDie(message, Project.MSG_WARN);
156             return;
157         }
158 
159         if (destFileUrl != null && destDirUrl != null)
160         {
161             final String message =
162                 Messages.getString("vfs.tasks/sync.too-many-destinations.error");
163             logOrDie(message, Project.MSG_WARN);
164             return;
165         }
166 
167         // Add the files of the includes attribute to the list
168         if (srcDirUrl != null && !srcDirUrl.equals(destDirUrl) && filesList != null && filesList.length() > 0)
169         {
170             if (!srcDirUrl.endsWith("/"))
171             {
172                 srcDirUrl += "/";
173             }
174             StringTokenizer tok = new StringTokenizer(filesList, ", \t\n\r\f", false);
175             while (tok.hasMoreTokens())
176             {
177                 String nextFile = tok.nextToken();
178 
179                 // Basic compatibility with Ant fileset for directories
180                 if (nextFile.endsWith("/**"))
181                 {
182                     nextFile = nextFile.substring(0, nextFile.length() - 2);
183                 }
184 
185                 final SourceInfo src = new SourceInfo();
186                 src.setFile(srcDirUrl + nextFile);
187                 addConfiguredSrc(src);
188             }
189         }
190 
191         if (srcFiles.size() == 0)
192         {
193             final String message = Messages.getString("vfs.tasks/sync.no-source-files.warn");
194             logOrDie(message, Project.MSG_WARN);
195             return;
196         }
197 
198         // Perform the sync
199         try
200         {
201             if (destFileUrl != null)
202             {
203                 handleSingleFile();
204             }
205             else
206             {
207                 handleFiles();
208             }
209         }
210         catch (final BuildException e)
211         {
212             throw e;
213         }
214         catch (final Exception e)
215         {
216             throw new BuildException(e.getMessage(), e);
217         }
218     }
219 
220 	protected void logOrDie(final String message, int level)
221 	{
222 		if (!isFailonerror())
223 		{
224 			log(message, level);
225 			return;
226 		}
227 		throw new BuildException(message);
228 	}
229 
230     /***
231      * Copies the source files to the destination.
232      */
233     private void handleFiles() throws Exception
234     {
235         // Locate the destination folder, and make sure it exists
236         final FileObject destFolder = resolveFile(destDirUrl);
237         destFolder.createFolder();
238 
239         // Locate the source files, and make sure they exist
240         FileName srcDirName = null;
241         if (srcDirUrl !=null )
242         {
243             srcDirName = resolveFile(srcDirUrl).getName();
244         }
245         final ArrayList srcs = new ArrayList();
246         for (int i = 0; i < srcFiles.size(); i++)
247         {
248             // Locate the source file, and make sure it exists
249             final SourceInfo src = (SourceInfo) srcFiles.get(i);
250             final FileObject srcFile = resolveFile(src.file);
251             if (!srcFile.exists())
252             {
253                 final String message =
254                     Messages.getString("vfs.tasks/sync.src-file-no-exist.warn", srcFile);
255                 
256                 logOrDie(message, Project.MSG_WARN);
257             }
258             else
259             {
260                 srcs.add(srcFile);
261             }
262         }
263 
264         // Scan the source files
265         final Set destFiles = new HashSet();
266         for (int i = 0; i < srcs.size(); i++)
267         {
268             final FileObject rootFile = (FileObject) srcs.get(i);
269             final FileName rootName = rootFile.getName();
270 
271             if (rootFile.getType() == FileType.FILE)
272             {
273                 // Build the destination file name
274                 String relName = null;
275                 if (srcDirName == null || !srcDirIsBase)
276                 {
277                     relName = rootName.getBaseName();
278                 }
279                 else
280                 {
281                     relName = srcDirName.getRelativeName(rootName);
282                 }
283                 final FileObject destFile = destFolder.resolveFile(relName, NameScope.DESCENDENT);
284 
285                 // Do the copy
286                 handleFile(destFiles, rootFile, destFile);
287             }
288             else
289             {
290                 // Find matching files
291                 // If srcDirIsBase is true, select also the sub-directories
292                 final FileObject[] files = rootFile.findFiles(srcDirIsBase ? Selectors.SELECT_ALL : Selectors.SELECT_FILES);
293 
294                 for (int j = 0; j < files.length; j++)
295                 {
296                     final FileObject srcFile = files[j];
297 
298                     // Build the destination file name
299                     String relName = null;
300                     if (srcDirName == null || !srcDirIsBase)
301                     {
302                         relName = rootName.getRelativeName(srcFile.getName());
303                     }
304                     else
305                     {
306                         relName = srcDirName.getRelativeName(srcFile.getName());
307                     }
308 
309                     final FileObject destFile =
310                         destFolder.resolveFile(relName, NameScope.DESCENDENT);
311 
312                     // Do the copy
313                     handleFile(destFiles, srcFile, destFile);
314                 }
315             }
316         }
317 
318         // Scan the destination files for files with no source file
319         if (detectMissingSourceFiles())
320         {
321             final FileObject[] allDestFiles = destFolder.findFiles(Selectors.SELECT_FILES);
322             for (int i = 0; i < allDestFiles.length; i++)
323             {
324                 final FileObject destFile = allDestFiles[i];
325                 if (!destFiles.contains(destFile))
326                 {
327                     handleMissingSourceFile(destFile);
328                 }
329             }
330         }
331     }
332 
333     /***
334      * Handles a single file, checking for collisions where more than one
335      * source file maps to the same destination file.
336      */
337     private void handleFile(final Set destFiles,
338                             final FileObject srcFile,
339                             final FileObject destFile) throws Exception
340 
341     {
342         // Check for duplicate source files
343         if (destFiles.contains(destFile))
344         {
345             final String message = Messages.getString("vfs.tasks/sync.duplicate-source-files.warn", destFile);
346             logOrDie(message, Project.MSG_WARN);
347         }
348         else
349         {
350             destFiles.add(destFile);
351         }
352 
353         // Handle the file
354         handleFile(srcFile, destFile);
355     }
356 
357     /***
358      * Copies a single file.
359      */
360     private void handleSingleFile() throws Exception
361     {
362         // Make sure there is exactly one source file, and that it exists
363         // and is a file.
364         if (srcFiles.size() > 1)
365         {
366             final String message =
367                 Messages.getString("vfs.tasks/sync.too-many-source-files.error");
368             logOrDie(message, Project.MSG_WARN);
369             return;
370         }
371         final SourceInfo src = (SourceInfo) srcFiles.get(0);
372         final FileObject srcFile = resolveFile(src.file);
373         if (srcFile.getType() != FileType.FILE)
374         {
375             final String message =
376                 Messages.getString("vfs.tasks/sync.source-not-file.error", srcFile);
377             logOrDie(message, Project.MSG_WARN);
378             return;
379         }
380 
381         // Locate the destination file
382         final FileObject destFile = resolveFile(destFileUrl);
383 
384         // Do the copy
385         handleFile(srcFile, destFile);
386     }
387 
388     /***
389      * Handles a single source file.
390      */
391     private void handleFile(final FileObject srcFile,
392                             final FileObject destFile)
393         throws Exception
394     {
395         if (!destFile.exists()
396             || srcFile.getContent().getLastModifiedTime() > destFile.getContent().getLastModifiedTime())
397         {
398             // Destination file is out-of-date
399             handleOutOfDateFile(srcFile, destFile);
400         }
401         else
402         {
403             // Destination file is up-to-date
404             handleUpToDateFile(srcFile, destFile);
405         }
406     }
407 
408     /***
409      * Handles an out-of-date file (a file where the destination file
410      * either doesn't exist, or is older than the source file).
411      * This implementation does nothing.
412      */
413     protected void handleOutOfDateFile(final FileObject srcFile,
414                                        final FileObject destFile)
415         throws Exception
416     {
417     }
418 
419     /***
420      * Handles an up-to-date file (where the destination file exists and is
421      * newer than the source file).  This implementation does nothing.
422      */
423     protected void handleUpToDateFile(final FileObject srcFile,
424                                       final FileObject destFile)
425         throws Exception
426     {
427     }
428 
429     /***
430      * Handles a destination for which there is no corresponding source file.
431      * This implementation does nothing.
432      */
433     protected void handleMissingSourceFile(final FileObject destFile)
434         throws Exception
435     {
436     }
437 
438     /***
439      * Check if this task cares about destination files with a missing source
440      * file.  This implementation returns false.
441      */
442     protected boolean detectMissingSourceFiles()
443     {
444         return false;
445     }
446 
447     /***
448      * Information about a source file.
449      */
450     public static class SourceInfo
451     {
452         private String file;
453 
454         public void setFile(final String file)
455         {
456             this.file = file;
457         }
458     }
459 
460 }