View Javadoc
1   /*
2    * Copyright (C) 2010-2014 Hamburg Sud and the contributors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.aludratest.service.file.impl;
17  
18  import java.io.BufferedReader;
19  import java.io.ByteArrayInputStream;
20  import java.io.ByteArrayOutputStream;
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.io.InputStreamReader;
24  import java.io.OutputStream;
25  import java.io.OutputStreamWriter;
26  import java.io.PrintWriter;
27  import java.io.Reader;
28  import java.io.StringReader;
29  import java.io.StringWriter;
30  import java.io.UnsupportedEncodingException;
31  import java.io.Writer;
32  import java.util.ArrayList;
33  import java.util.List;
34  
35  import org.aludratest.exception.AutomationException;
36  import org.aludratest.exception.FunctionalFailure;
37  import org.aludratest.exception.TechnicalException;
38  import org.aludratest.service.SystemConnector;
39  import org.aludratest.service.file.FileCondition;
40  import org.aludratest.service.file.FileFilter;
41  import org.aludratest.service.file.FileInfo;
42  import org.aludratest.service.file.FileInteraction;
43  import org.aludratest.service.file.FileService;
44  import org.aludratest.service.file.FileVerification;
45  import org.aludratest.service.file.filter.RegexFilePathFilter;
46  import org.aludratest.service.file.util.FileUtil;
47  import org.aludratest.testcase.event.attachment.Attachment;
48  import org.aludratest.util.poll.PollService;
49  import org.aludratest.util.poll.PolledTask;
50  import org.apache.commons.vfs2.AllFileSelector;
51  import org.apache.commons.vfs2.FileObject;
52  import org.apache.commons.vfs2.FileSelector;
53  import org.apache.commons.vfs2.FileSystemException;
54  import org.apache.commons.vfs2.FileType;
55  import org.databene.commons.IOUtil;
56  import org.slf4j.Logger;
57  import org.slf4j.LoggerFactory;
58  
59  /**
60   * Implements all Action interfaces of the {@link FileService} in a single class:
61   * {@link FileInteraction}, {@link FileVerification} and {@link FileCondition}.
62   * @author Volker Bergmann
63   */
64  public class FileActionImpl implements FileInteraction, FileVerification, FileCondition {
65  
66      private static final Logger LOGGER = LoggerFactory.getLogger(FileActionImpl.class);
67  
68      /** The configuration for this service instance. */
69      private FileServiceConfiguration configuration;
70  
71      /** A polling service to delegate poll operations to. */
72      private PollService pollService;
73  
74      /** Constructor taking the configuration.
75       *  @param configuration */
76      public FileActionImpl(FileServiceConfiguration configuration) {
77          this.configuration = configuration;
78          this.pollService = new PollService(configuration.getTimeout(), configuration.getPollingDelay());
79          getChildren(getRootFolder());
80      }
81  
82      @Override
83      public void setSystemConnector(SystemConnector systemConnector) {
84          // empty implementation
85      }
86  
87      /** Provides the root folder of the service instance. */
88      @Override
89      public String getRootFolder() {
90          return "/";
91      }
92  
93      /** Lists all child elements of the given folder. */
94      @Override
95      public final List<String> getChildren(String filePath) {
96          FileUtil.verifyFilePath(filePath);
97          return getChildren(filePath, (FileFilter) null);
98      }
99  
100     /** Lists all child elements of the given folder which match the given regular expression. */
101     @Override
102     public List<String> getChildren(String filePath, String filterRegex) {
103         FileUtil.verifyFilePath(filePath);
104         return getChildren(filePath, new RegexFilePathFilter(filterRegex));
105     }
106 
107     /** Lists all child elements of the given folder which match the filter. */
108     @Override
109     public List<String> getChildren(String filePath, FileFilter filter) {
110         FileUtil.verifyFilePath(filePath);
111         try {
112             FileObject parent = configuration.getFileObject(filePath);
113             parent.refresh();
114             FileObject[] candidates = parent.getChildren();
115             List<String> filePaths = new ArrayList<String>();
116             if (candidates != null) {
117                 for (FileObject candidate : candidates) {
118                     if (filter == null || filter.accept(new FileInfo(candidate))) {
119                         filePaths.add(pathFromRoot(candidate));
120                     }
121                 }
122             }
123             if (filePaths.size() > 0) {
124                 LOGGER.debug("Found children: {}", filePaths);
125             } else {
126                 LOGGER.debug("No children found for filter {}", filter);
127             }
128             return filePaths;
129         } catch (FileSystemException e) {
130             throw new TechnicalException("Error retrivieving child objects", e);
131         }
132     }
133 
134     /** Creates a directory. */
135     @Override
136     public void createDirectory(String filePath) {
137         assertWritingPermitted("createDirectory()");
138         FileUtil.verifyFilePath(filePath);
139         try {
140             getFileObject(filePath).createFolder();
141             LOGGER.debug("Created directory {}", filePath);
142         } catch (FileSystemException e) {
143             throw new TechnicalException("Directory creation failed", e);
144         }
145     }
146 
147     /** Renames or moves a file or folder.
148      * @param fromPath the file/folder to rename/move
149      * @param toPath the new name/location of the file/folder
150      * @param overwrite flag which indicates if an existing file may be overwritten by the operation
151      * @return true if a formerly existing file was overwritten.
152      * @throws FilePresentException if a file was already present and overwriting was disabled. */
153     @Override
154     public boolean move(String fromPath, String toPath, boolean overwrite) {
155         assertWritingPermitted("move()");
156         FileUtil.verifyFilePath(fromPath);
157         FileUtil.verifyFilePath(toPath);
158         FileObject target = getFileObject(toPath);
159         boolean existedBefore = checkWritable(target, overwrite);
160         try {
161             getOrCreateDirectory(target.getParent());
162             getFileObject(fromPath).moveTo(target);
163             LOGGER.debug("Moved {} to {}", fromPath, toPath);
164             return existedBefore;
165         } catch (FileSystemException e) {
166             throw new TechnicalException("Error moving file" + fromPath + " -> " + toPath, e);
167         }
168     }
169 
170     /** Copies a file or folder.
171      *  @param fromPath the file/folder to copy
172      *  @param toPath the name/location of the copy
173      *  @param overwrite flag which indicates if an existing file may be overwritten by the operation
174      *  @return true if a formerly existing file was overwritten.
175      *  @throws FilePresentException if a file was already present and overwriting was disabled. */
176     @Override
177     public boolean copy(String fromPath, String toPath, boolean overwrite) {
178         assertWritingPermitted("copy()");
179         FileUtil.verifyFilePath(fromPath);
180         FileUtil.verifyFilePath(toPath);
181         FileObject target = getFileObject(toPath);
182         boolean existedBefore = checkWritable(target, overwrite);
183         try {
184             FileObject source = getFileObject(fromPath);
185             FileSelector sourceSelector = new FilePathSelector(source.getName().getPath());
186             target.copyFrom(source, sourceSelector);
187             LOGGER.debug("Copied {} to {}", fromPath, toPath);
188             return existedBefore;
189         } catch (FileSystemException e) {
190             throw new TechnicalException("Error copying file " + fromPath + " -> " + toPath, e);
191         }
192     }
193 
194     /** Deletes a file or folder. */
195     @Override
196     public void delete(String filePath) {
197         assertWritingPermitted("delete()");
198         FileUtil.verifyFilePath(filePath);
199         try {
200             getFileObject(filePath).delete(new AllFileSelector());
201             LOGGER.debug("Deleted {}", filePath);
202         } catch (FileSystemException e) {
203             throw new TechnicalException("Error deleting file", e);
204         }
205     }
206 
207     /** Creates a text file with the provided content.
208      *  @param filePath the path of the file to save
209      *  @param content the text to save as file content
210      *  @param overwrite flag which indicates if an existing file may be overwritten by the operation
211      *  @return true if a formerly existing file was overwritten.
212      *  @throws FilePresentException if a file was already present and overwriting was disabled. */
213     @Override
214     public boolean writeTextFile(String filePath, String content, boolean overwrite) {
215         assertWritingPermitted("writeTextFile()");
216         FileUtil.verifyFilePath(filePath);
217         return writeTextFile(filePath, new StringReader(content), overwrite);
218     }
219 
220     /** Creates a text file and writes to it all content provided by the source Reader.
221      *  @param filePath the path of the file to save
222      *  @param source a {@link Reader} which provides the file content
223      *  @param overwrite flag which indicates if an existing file may be overwritten by the operation
224      *  @return true if a formerly existing file was overwritten.
225      *  @throws FilePresentException if a file was already present and overwriting was disabled. */
226     @Override
227     public boolean writeTextFile(String filePath, Reader source, boolean overwrite) {
228         assertWritingPermitted("writeTextFile()");
229         FileUtil.verifyFilePath(filePath);
230         String encoding = configuration.getEncoding();
231         Writer writer = null;
232         BufferedReader reader = null;
233         try {
234             String linefeed = configuration.getLinefeed();
235             FileObject target = getFileObject(filePath);
236             boolean existedBefore = checkWritable(target, overwrite);
237             writer = new OutputStreamWriter(target.getContent().getOutputStream(), encoding);
238             reader = new BufferedReader(source);
239             boolean firstLine = true;
240             String line;
241             while ((line = reader.readLine()) != null) {
242                 if (!firstLine) {
243                     writer.write(linefeed);
244                 }
245                 writer.write(line);
246                 firstLine = false;
247             }
248             LOGGER.debug("Wrote text file {}", filePath);
249             return existedBefore;
250         } catch (UnsupportedEncodingException e) {
251             throw new TechnicalException("Unsupported Encoding:" + encoding, e);
252         } catch (Exception e) {
253             throw new TechnicalException("Error writing text file", e);
254         } finally {
255             IOUtil.close(writer);
256             IOUtil.close(reader);
257         }
258     }
259 
260     /** Creates a binary file with the provided content.
261      *  @param filePath the path of the file to save
262      *  @param bytes the file content to write
263      *  @param overwrite flag which indicates if an existing file may be overwritten by the operation
264      *  @return true if a formerly existing file was overwritten.
265      *  @throws FilePresentException if a file was already present and overwriting was disabled. */
266     @Override
267     public boolean writeBinaryFile(String filePath, byte[] bytes, boolean overwrite) {
268         assertWritingPermitted("writeBinaryFile()");
269         ByteArrayInputStream in = new ByteArrayInputStream(bytes);
270         return writeBinaryFile(filePath, in, overwrite);
271     }
272 
273     /** Creates a binary file and writes to it all content provided by the source {@link InputStream}.
274      *  @param filePath the path of the file to save
275      *  @param source an {@link InputStream} which provides the content to write to the file
276      *  @param overwrite flag which indicates if an existing file may be overwritten by the operation
277      *  @return true if a formerly existing file was overwritten.
278      *  @throws FilePresentException if a file was already present and overwriting was disabled. */
279     @Override
280     public boolean writeBinaryFile(String filePath, InputStream source, boolean overwrite) {
281         assertWritingPermitted("writeBinaryFile()");
282         FileUtil.verifyFilePath(filePath);
283         OutputStream out = null;
284         try {
285             FileObject target = getFileObject(filePath);
286             boolean existedBefore = checkWritable(target, overwrite);
287             out = target.getContent().getOutputStream();
288             IOUtil.transfer(source, out);
289             LOGGER.debug("Wrote binary file {}", filePath);
290             return existedBefore;
291         } catch (Exception e) {
292             throw new TechnicalException("Error writing text file", e);
293         } finally {
294             IOUtil.close(out);
295         }
296     }
297 
298     /** Reads a text file and provides its content as String. */
299     @Override
300     public String readTextFile(String filePath) {
301         FileUtil.verifyFilePath(filePath);
302         BufferedReader reader = null;
303         try {
304             StringWriter writer = new StringWriter();
305             PrintWriter printer = new PrintWriter(writer);
306             reader = getReaderForTextFile(filePath);
307             boolean firstLine = true;
308             String line;
309             while ((line = reader.readLine()) != null) {
310                 if (!firstLine) {
311                     printer.println();
312                 }
313                 printer.write(line);
314                 firstLine = false;
315             }
316             LOGGER.debug("Text file read: {}", filePath);
317             return writer.toString();
318         } catch (IOException e) {
319             throw new TechnicalException("Error reading text file", e);
320         } finally {
321             IOUtil.close(reader); // InputStream is closed by the Reader
322         }
323     }
324 
325     /** Creates a {@link Reader} for accessing the content of a text file. */
326     @Override
327     public BufferedReader getReaderForTextFile(String filePath) {
328         FileUtil.verifyFilePath(filePath);
329         String encoding = configuration.getEncoding();
330         try {
331             LOGGER.debug("Providing reader for text file: {}", filePath);
332             return new BufferedReader(new InputStreamReader(getInputStreamForFile(filePath), encoding));
333         } catch (UnsupportedEncodingException e) {
334             throw new TechnicalException("Unsupported Encoding:" + encoding, e);
335         }
336     }
337 
338     /** Reads a binary file and provides its content as an array of bytes. */
339     @Override
340     public byte[] readBinaryFile(String filePath) {
341         FileUtil.verifyFilePath(filePath);
342         InputStream in = null;
343         try {
344             in = getInputStreamForFile(filePath);
345             ByteArrayOutputStream out = new ByteArrayOutputStream();
346             IOUtil.transfer(in, out);
347             LOGGER.debug("Binary file read: {}", filePath);
348             return out.toByteArray();
349         } catch (IOException e) {
350             throw new TechnicalException("Error reading binary file", e);
351         } finally {
352             IOUtil.close(in);
353         }
354     }
355 
356     /** Creates an {@link InputStream} for accessing the content of a file. */
357     @Override
358     public InputStream getInputStreamForFile(String filePath) {
359         FileUtil.verifyFilePath(filePath);
360         FileObject file = getFileObject(filePath);
361         try {
362             LOGGER.debug("Providing InputStream for binary file: {}", filePath);
363             return file.getContent().getInputStream();
364         } catch (FileSystemException e) {
365             throw new TechnicalException("Error opening InputStream", e);
366         }
367     }
368 
369     /** Polls the file system for a given file until it is found or a timeout is exceeded.
370      *  Timeout and the maximum number of polls are retrieved from the
371      *  {@link FileServiceConfiguration}.
372      *  @throws FunctionalFailure if the file was not found within the timeout */
373     @Override
374     public void waitUntilExists(String elementType, String filePath) {
375         FileUtil.verifyFilePath(filePath);
376         pollService.poll(new WaitForFileTask(filePath, true));
377     }
378 
379     /** Polls the file system for a given file until it has disappeared or a timeout is exceeded.
380      *  Timeout and the maximum number of polls are retrieved from the
381      *  {@link FileServiceConfiguration}.
382      *  @throws FunctionalFailure if the file was not found within the timeout */
383     @Override
384     public void waitUntilNotExists(String filePath) {
385         FileUtil.verifyFilePath(filePath);
386         pollService.poll(new WaitForFileTask(filePath, false));
387     }
388 
389     /** Polls the given directory until the filter finds a match or a timeout is exceeded.
390      *  Timeout and the maximum number of polls are retrieved from the
391      *  {@link FileServiceConfiguration}.
392      *  @throws AutomationException if the file was not found within the timeout */
393     @Override
394     public String waitForFirstMatch(String parentPath, FileFilter filter) {
395         FileUtil.verifyFilePath(parentPath);
396         if (!exists(parentPath)) {
397             throw new AutomationException("Directory not found: " + parentPath);
398         }
399         if (!isDirectory(parentPath)) {
400             throw new AutomationException("Not a directory: " + parentPath);
401         }
402         if (filter == null) {
403             throw new AutomationException("Filter is null");
404         }
405         return pollService.poll(new WaitForFirstMatchTask(parentPath, filter));
406     }
407 
408     /** Expects a file to exist.
409      *  @throws AutomationException if the file was not found. */
410     @Override
411     public void assertPresence(String filePath) {
412         FileUtil.verifyFilePath(filePath);
413         if (!exists(filePath)) {
414             throw new AutomationException("Expected file not present: " + filePath);
415         } else {
416             LOGGER.debug("File exists as expected: {}", filePath);
417         }
418 
419     }
420 
421     /** Expects a file to be absent.
422      *  @throws AutomationException if the file was encountered. */
423     @Override
424     public void assertAbsence(String filePath) {
425         FileUtil.verifyFilePath(filePath);
426         if (exists(filePath)) {
427             throw new AutomationException("File expected to be absent: " + filePath);
428         } else {
429             LOGGER.debug("File is absent as expected: {}", filePath);
430         }
431     }
432 
433     /** Tells if a file or folder with the given path exists. */
434     @Override
435     public boolean exists(String filePath) {
436         FileUtil.verifyFilePath(filePath);
437         try {
438             FileObject file = getFileObject(filePath);
439             file.refresh();
440             boolean result = file.exists();
441             LOGGER.debug("File '{}' {}", filePath, (result ? "exists" : "does not exist"));
442             return result;
443         } catch (FileSystemException e) {
444             throw new TechnicalException("Error checking file presence", e);
445         }
446     }
447 
448     @Override
449     public void assertFile(String filePath) {
450         FileUtil.verifyFilePath(filePath);
451         assertPresence(filePath);
452         if (isDirectory(filePath)) {
453             throw new FunctionalFailure("Not a file: " + filePath);
454         } else {
455             LOGGER.debug("File is not a directory: {}", filePath);
456         }
457     }
458 
459     @Override
460     public void assertDirectory(String filePath) {
461         FileUtil.verifyFilePath(filePath);
462         assertPresence(filePath);
463         if (!isDirectory(filePath)) {
464             throw new FunctionalFailure("Not a directory: " + filePath);
465         } else {
466             LOGGER.debug("File is not directory: {}", filePath);
467         }
468     }
469 
470     /** Tells if the given path represents a directory. */
471     @Override
472     public boolean isDirectory(String filePath) {
473         FileUtil.verifyFilePath(filePath);
474         try {
475             boolean result = getFileObject(filePath).getType() == FileType.FOLDER;
476             LOGGER.debug("{} is {}", filePath, (result ? "a folder" : "not a folder"));
477             return result;
478         } catch (FileSystemException e) {
479             throw new TechnicalException("Error accessing file or folder", e);
480         }
481     }
482 
483     @Override
484     public List<Attachment> createAttachments(Object object, String label) {
485         throw new TechnicalException("Not supported");
486     }
487 
488     @Override
489     public List<Attachment> createDebugAttachments() {
490         return null;
491     }
492 
493     // private helpers ---------------------------------------------------------
494 
495     private FileObject getFileObject(String pathFromRoot) {
496         return configuration.getFileObject(pathFromRoot);
497     }
498 
499     private String pathFromRoot(FileObject file) {
500         return configuration.pathFromRoot(file);
501     }
502 
503     private boolean checkWritable(FileObject file, boolean overwrite) {
504         try {
505             if (file.exists()) {
506                 if (overwrite) {
507                     file.delete();
508                 } else {
509                     throw new FunctionalFailure("File expected to be absent: " + pathFromRoot(file));
510                 }
511                 return true;
512             } else {
513                 return false;
514             }
515         } catch (FileSystemException e) {
516             throw new TechnicalException("Error checking file", e);
517         }
518     }
519 
520     private void getOrCreateDirectory(FileObject directory) {
521         FileObject root = configuration.getRootFolder();
522         if (!root.equals(directory)) {
523             try {
524                 getOrCreateDirectory(directory.getParent());
525                 directory.createFolder();
526             } catch (FileSystemException e) {
527                 throw new TechnicalException("Error creating directory", e);
528             }
529         }
530     }
531 
532     private void assertWritingPermitted(String operation) {
533         if (!configuration.isWritingPermitted()) {
534             throw new AutomationException("Invoked " + operation + " on a write protected file system");
535         }
536     }
537 
538     private class WaitForFileTask implements PolledTask<String> {
539 
540         private String filePath;
541         private boolean awaitExistence;
542 
543         public WaitForFileTask(String filePath, boolean awaitExistence) {
544             this.filePath = filePath;
545             this.awaitExistence = awaitExistence;
546         }
547 
548         @Override
549         public String run() {
550             FileObject file = getFileObject(filePath);
551             try {
552                 if (!awaitExistence) {
553                     // This is a workaround for VFS 2.0's flaws in the
554                     // handling of attached/detached state and caching:
555                     FileObject parent = file.getParent();
556                     parent.getType(); // assure that parent folder is attached
557                     parent.refresh(); // detach parent folder and clear child object cache
558                     // (works only if attached before)
559                     // ...end of workaround
560                 }
561                 file.refresh();
562                 if (file.exists()) {
563                     LOGGER.debug("File found: {}", filePath);
564                     return (awaitExistence ? filePath : null);
565                 } else {
566                     LOGGER.debug("File not found: {}", filePath);
567                     return (awaitExistence ? null : filePath);
568                 }
569             } catch (FileSystemException e) {
570                 throw new TechnicalException("Error checking presence of file ", e);
571             }
572         }
573 
574         @Override
575         public String timedOut() {
576             String expectedState = (awaitExistence ? "found" : "removed");
577             throw new FunctionalFailure("File not " + expectedState + " within timeout: " + filePath);
578         }
579 
580         @Override
581         public String toString() {
582             return getClass().getSimpleName() + "(" + filePath + ")";
583         }
584 
585     }
586 
587     /**
588      * {@link PolledTask} that waits for the first occurrence of a file that matches the filter.
589      * @author Volker Bergmann
590      */
591     public class WaitForFirstMatchTask implements PolledTask<String> {
592 
593         private String parentPath;
594         private FileFilter filter;
595 
596         /** Constructor
597          *  @param parentPath the folder in which to search
598          *  @param filter the filter to apply in search */
599         public WaitForFirstMatchTask(String parentPath, FileFilter filter) {
600             this.parentPath = parentPath;
601             this.filter = filter;
602         }
603 
604         @Override
605         public String run() {
606             List<String> children = getChildren(parentPath, filter);
607             if (children.size() > 0) {
608                 String result = children.get(0);
609                 LOGGER.debug("File found: {}", result);
610                 return result;
611             } else {
612                 LOGGER.debug("No match found for {}", filter);
613                 return null;
614             }
615         }
616 
617         @Override
618         public String timedOut() {
619             throw new FunctionalFailure("No match found for filter " + filter);
620         }
621 
622         @Override
623         public String toString() {
624             return getClass().getSimpleName() + "(" + filter.toString() + ")";
625         }
626 
627     }
628 
629 }