001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.commons.vfs.tasks;
018:
019: import org.apache.commons.vfs.FileName;
020: import org.apache.commons.vfs.FileObject;
021: import org.apache.commons.vfs.FileType;
022: import org.apache.commons.vfs.NameScope;
023: import org.apache.commons.vfs.Selectors;
024: import org.apache.commons.vfs.util.Messages;
025: import org.apache.tools.ant.BuildException;
026: import org.apache.tools.ant.Project;
027:
028: import java.util.ArrayList;
029: import java.util.HashSet;
030: import java.util.Set;
031: import java.util.StringTokenizer;
032:
033: /**
034: * An abstract file synchronization task. Scans a set of source files and
035: * folders, and a destination folder, and performs actions on missing and
036: * out-of-date files. Specifically, performs actions on the following:
037: * <ul>
038: * <li>Missing destination file.
039: * <li>Missing source file.
040: * <li>Out-of-date destination file.
041: * <li>Up-to-date destination file.
042: * </ul>
043: *
044: * @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a>
045: * @version $Revision: 480428 $ $Date: 2006-11-28 22:15:24 -0800 (Tue, 28 Nov 2006) $
046: * @todo Deal with case where dest file maps to a child of one of the source files
047: * @todo Deal with case where dest file already exists and is incorrect type (not file, not a folder)
048: * @todo Use visitors
049: * @todo Add default excludes
050: * @todo Allow selector, mapper, filters, etc to be specified.
051: * @todo Handle source/dest directories as well
052: * @todo Allow selector to be specified for choosing which dest files to sync
053: */
054: public abstract class AbstractSyncTask extends VfsTask {
055: private final ArrayList srcFiles = new ArrayList();
056: private String destFileUrl;
057: private String destDirUrl;
058: private String srcDirUrl;
059: private boolean srcDirIsBase;
060: private boolean failonerror = true;
061: private String filesList;
062:
063: /**
064: * Sets the destination file.
065: */
066: public void setDestFile(final String destFile) {
067: this .destFileUrl = destFile;
068: }
069:
070: /**
071: * Sets the destination directory.
072: */
073: public void setDestDir(final String destDir) {
074: this .destDirUrl = destDir;
075: }
076:
077: /**
078: * Sets the source file
079: */
080: public void setSrc(final String srcFile) {
081: final SourceInfo src = new SourceInfo();
082: src.setFile(srcFile);
083: addConfiguredSrc(src);
084: }
085:
086: /**
087: * Sets the source directory
088: */
089: public void setSrcDir(final String srcDir) {
090: this .srcDirUrl = srcDir;
091: }
092:
093: /**
094: * Sets whether the source directory should be consider as the base directory.
095: */
096: public void setSrcDirIsBase(final boolean srcDirIsBase) {
097: this .srcDirIsBase = srcDirIsBase;
098: }
099:
100: /**
101: * Sets whether we should fail if there was an error or not
102: */
103: public void setFailonerror(final boolean failonerror) {
104: this .failonerror = failonerror;
105: }
106:
107: /**
108: * Sets whether we should fail if there was an error or not
109: */
110: public boolean isFailonerror() {
111: return failonerror;
112: }
113:
114: /**
115: * Sets the files to includes
116: */
117: public void setIncludes(final String filesList) {
118: this .filesList = filesList;
119: }
120:
121: /**
122: * Adds a nested <src> element.
123: */
124: public void addConfiguredSrc(final SourceInfo srcInfo)
125: throws BuildException {
126: if (srcInfo.file == null) {
127: final String message = Messages
128: .getString("vfs.tasks/sync.no-source-file.error");
129: throw new BuildException(message);
130: }
131: srcFiles.add(srcInfo);
132: }
133:
134: /**
135: * Executes this task.
136: */
137: public void execute() throws BuildException {
138: // Validate
139: if (destFileUrl == null && destDirUrl == null) {
140: final String message = Messages
141: .getString("vfs.tasks/sync.no-destination.error");
142: logOrDie(message, Project.MSG_WARN);
143: return;
144: }
145:
146: if (destFileUrl != null && destDirUrl != null) {
147: final String message = Messages
148: .getString("vfs.tasks/sync.too-many-destinations.error");
149: logOrDie(message, Project.MSG_WARN);
150: return;
151: }
152:
153: // Add the files of the includes attribute to the list
154: if (srcDirUrl != null && !srcDirUrl.equals(destDirUrl)
155: && filesList != null && filesList.length() > 0) {
156: if (!srcDirUrl.endsWith("/")) {
157: srcDirUrl += "/";
158: }
159: StringTokenizer tok = new StringTokenizer(filesList,
160: ", \t\n\r\f", false);
161: while (tok.hasMoreTokens()) {
162: String nextFile = tok.nextToken();
163:
164: // Basic compatibility with Ant fileset for directories
165: if (nextFile.endsWith("/**")) {
166: nextFile = nextFile.substring(0,
167: nextFile.length() - 2);
168: }
169:
170: final SourceInfo src = new SourceInfo();
171: src.setFile(srcDirUrl + nextFile);
172: addConfiguredSrc(src);
173: }
174: }
175:
176: if (srcFiles.size() == 0) {
177: final String message = Messages
178: .getString("vfs.tasks/sync.no-source-files.warn");
179: logOrDie(message, Project.MSG_WARN);
180: return;
181: }
182:
183: // Perform the sync
184: try {
185: if (destFileUrl != null) {
186: handleSingleFile();
187: } else {
188: handleFiles();
189: }
190: } catch (final BuildException e) {
191: throw e;
192: } catch (final Exception e) {
193: throw new BuildException(e.getMessage(), e);
194: }
195: }
196:
197: protected void logOrDie(final String message, int level) {
198: if (!isFailonerror()) {
199: log(message, level);
200: return;
201: }
202: throw new BuildException(message);
203: }
204:
205: /**
206: * Copies the source files to the destination.
207: */
208: private void handleFiles() throws Exception {
209: // Locate the destination folder, and make sure it exists
210: final FileObject destFolder = resolveFile(destDirUrl);
211: destFolder.createFolder();
212:
213: // Locate the source files, and make sure they exist
214: FileName srcDirName = null;
215: if (srcDirUrl != null) {
216: srcDirName = resolveFile(srcDirUrl).getName();
217: }
218: final ArrayList srcs = new ArrayList();
219: for (int i = 0; i < srcFiles.size(); i++) {
220: // Locate the source file, and make sure it exists
221: final SourceInfo src = (SourceInfo) srcFiles.get(i);
222: final FileObject srcFile = resolveFile(src.file);
223: if (!srcFile.exists()) {
224: final String message = Messages.getString(
225: "vfs.tasks/sync.src-file-no-exist.warn",
226: srcFile);
227:
228: logOrDie(message, Project.MSG_WARN);
229: } else {
230: srcs.add(srcFile);
231: }
232: }
233:
234: // Scan the source files
235: final Set destFiles = new HashSet();
236: for (int i = 0; i < srcs.size(); i++) {
237: final FileObject rootFile = (FileObject) srcs.get(i);
238: final FileName rootName = rootFile.getName();
239:
240: if (rootFile.getType() == FileType.FILE) {
241: // Build the destination file name
242: String relName = null;
243: if (srcDirName == null || !srcDirIsBase) {
244: relName = rootName.getBaseName();
245: } else {
246: relName = srcDirName.getRelativeName(rootName);
247: }
248: final FileObject destFile = destFolder.resolveFile(
249: relName, NameScope.DESCENDENT);
250:
251: // Do the copy
252: handleFile(destFiles, rootFile, destFile);
253: } else {
254: // Find matching files
255: // If srcDirIsBase is true, select also the sub-directories
256: final FileObject[] files = rootFile
257: .findFiles(srcDirIsBase ? Selectors.SELECT_ALL
258: : Selectors.SELECT_FILES);
259:
260: for (int j = 0; j < files.length; j++) {
261: final FileObject srcFile = files[j];
262:
263: // Build the destination file name
264: String relName = null;
265: if (srcDirName == null || !srcDirIsBase) {
266: relName = rootName.getRelativeName(srcFile
267: .getName());
268: } else {
269: relName = srcDirName.getRelativeName(srcFile
270: .getName());
271: }
272:
273: final FileObject destFile = destFolder.resolveFile(
274: relName, NameScope.DESCENDENT);
275:
276: // Do the copy
277: handleFile(destFiles, srcFile, destFile);
278: }
279: }
280: }
281:
282: // Scan the destination files for files with no source file
283: if (detectMissingSourceFiles()) {
284: final FileObject[] allDestFiles = destFolder
285: .findFiles(Selectors.SELECT_FILES);
286: for (int i = 0; i < allDestFiles.length; i++) {
287: final FileObject destFile = allDestFiles[i];
288: if (!destFiles.contains(destFile)) {
289: handleMissingSourceFile(destFile);
290: }
291: }
292: }
293: }
294:
295: /**
296: * Handles a single file, checking for collisions where more than one
297: * source file maps to the same destination file.
298: */
299: private void handleFile(final Set destFiles,
300: final FileObject srcFile, final FileObject destFile)
301: throws Exception
302:
303: {
304: // Check for duplicate source files
305: if (destFiles.contains(destFile)) {
306: final String message = Messages.getString(
307: "vfs.tasks/sync.duplicate-source-files.warn",
308: destFile);
309: logOrDie(message, Project.MSG_WARN);
310: } else {
311: destFiles.add(destFile);
312: }
313:
314: // Handle the file
315: handleFile(srcFile, destFile);
316: }
317:
318: /**
319: * Copies a single file.
320: */
321: private void handleSingleFile() throws Exception {
322: // Make sure there is exactly one source file, and that it exists
323: // and is a file.
324: if (srcFiles.size() > 1) {
325: final String message = Messages
326: .getString("vfs.tasks/sync.too-many-source-files.error");
327: logOrDie(message, Project.MSG_WARN);
328: return;
329: }
330: final SourceInfo src = (SourceInfo) srcFiles.get(0);
331: final FileObject srcFile = resolveFile(src.file);
332: if (srcFile.getType() != FileType.FILE) {
333: final String message = Messages.getString(
334: "vfs.tasks/sync.source-not-file.error", srcFile);
335: logOrDie(message, Project.MSG_WARN);
336: return;
337: }
338:
339: // Locate the destination file
340: final FileObject destFile = resolveFile(destFileUrl);
341:
342: // Do the copy
343: handleFile(srcFile, destFile);
344: }
345:
346: /**
347: * Handles a single source file.
348: */
349: private void handleFile(final FileObject srcFile,
350: final FileObject destFile) throws Exception {
351: if (!destFile.exists()
352: || srcFile.getContent().getLastModifiedTime() > destFile
353: .getContent().getLastModifiedTime()) {
354: // Destination file is out-of-date
355: handleOutOfDateFile(srcFile, destFile);
356: } else {
357: // Destination file is up-to-date
358: handleUpToDateFile(srcFile, destFile);
359: }
360: }
361:
362: /**
363: * Handles an out-of-date file (a file where the destination file
364: * either doesn't exist, or is older than the source file).
365: * This implementation does nothing.
366: */
367: protected void handleOutOfDateFile(final FileObject srcFile,
368: final FileObject destFile) throws Exception {
369: }
370:
371: /**
372: * Handles an up-to-date file (where the destination file exists and is
373: * newer than the source file). This implementation does nothing.
374: */
375: protected void handleUpToDateFile(final FileObject srcFile,
376: final FileObject destFile) throws Exception {
377: }
378:
379: /**
380: * Handles a destination for which there is no corresponding source file.
381: * This implementation does nothing.
382: */
383: protected void handleMissingSourceFile(final FileObject destFile)
384: throws Exception {
385: }
386:
387: /**
388: * Check if this task cares about destination files with a missing source
389: * file. This implementation returns false.
390: */
391: protected boolean detectMissingSourceFiles() {
392: return false;
393: }
394:
395: /**
396: * Information about a source file.
397: */
398: public static class SourceInfo {
399: private String file;
400:
401: public void setFile(final String file) {
402: this.file = file;
403: }
404: }
405:
406: }
|