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 }