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.transaction.file;
18  
19  import static junit.framework.Assert.fail;
20  
21  import java.io.BufferedReader;
22  import java.io.File;
23  import java.io.FileInputStream;
24  import java.io.FileOutputStream;
25  import java.io.IOException;
26  import java.io.InputStream;
27  import java.io.InputStreamReader;
28  import java.io.OutputStream;
29  import java.io.PrintStream;
30  import java.util.concurrent.TimeUnit;
31  
32  import junit.framework.JUnit4TestAdapter;
33  import static junit.framework.Assert.*;
34  import org.junit.Test;
35  
36  import org.apache.commons.transaction.file.FileResourceManager.FileResource;
37  import org.apache.commons.transaction.resource.ResourceException;
38  import org.apache.commons.transaction.util.FileHelper;
39  import org.apache.commons.transaction.util.RendezvousBarrier;
40  
41  public class TxFileResourceManagerTest {
42  
43      private static final String ENCODING = "ISO-8859-15";
44  
45      // XXX INCREASE THIS WHEN DEBUGGING OTHERWISE THE BARRIER WILL TIME OUT
46      // AFTER TWO SECONDS
47      // MOST LIKELY CONFUSING YOU COMPLETELY
48      private static final long BARRIER_TIMEOUT = 200000;
49  
50      private static final String rootPath = "d:/tmp/content";
51  
52      private static final String tmpDir = "d:/tmp/txlogs";
53  
54      private static String msg;
55  
56      private static void reset() {
57          removeRec(rootPath);
58          removeRec(tmpDir);
59      }
60  
61      private static final String[] INITIAL_FILES = new String[] { rootPath + "/olli/Hubert6",
62              rootPath + "/olli/Hubert" };
63  
64      private static void removeRec(String dirPath) {
65          FileHelper.removeRecursive(new File(dirPath));
66      }
67  
68      private static void createInitialFiles() {
69          createFiles(INITIAL_FILES);
70      }
71  
72      public static junit.framework.Test suite() {
73          return new JUnit4TestAdapter(TxFileResourceManagerTest.class);
74      }
75  
76      public static void main(java.lang.String[] args) {
77          junit.textui.TestRunner.run(suite());
78      }
79  
80      // XXX need this, as JUnit seems to print only part of these strings
81      private static void report(String should, String is) {
82          if (!is.equals(should)) {
83              fail("\nWrong output:\n'" + is + "'\nShould be:\n'" + should + "'\n");
84          }
85      }
86  
87      private static final void createFiles(String[] filePaths) {
88          createFiles(filePaths, null, null);
89      }
90  
91      private static final void createFiles(String[] filePaths, String dirPath) {
92          createFiles(filePaths, null, dirPath);
93      }
94  
95      private static final void createFiles(String[] filePaths, String[] contents) {
96          createFiles(filePaths, contents, null);
97      }
98  
99      private static final void createFiles(String[] filePaths, String[] contents, String dirPath) {
100         for (int i = 0; i < filePaths.length; i++) {
101             String filePath = filePaths[i];
102             File file;
103             if (dirPath != null) {
104                 file = new File(new File(dirPath), filePath);
105             } else {
106                 file = new File(filePath);
107             }
108             file.getParentFile().mkdirs();
109             try {
110                 file.delete();
111                 file.createNewFile();
112                 String content = null;
113                 if (contents != null && contents.length > i) {
114                     content = contents[i];
115                 }
116                 if (content != null) {
117                     FileOutputStream stream = new FileOutputStream(file);
118                     stream.write(contents[i].getBytes(ENCODING));
119                     stream.close();
120                 }
121             } catch (IOException e) {
122             }
123         }
124     }
125 
126     private static final void checkIsEmpty(String dirPath) {
127         checkExactlyContains(dirPath, null);
128     }
129 
130     private static final void checkExactlyContains(String dirPath, String[] fileNames) {
131         checkExactlyContains(dirPath, fileNames, null);
132     }
133 
134     private static final void checkExactlyContains(String dirPath, String[] fileNames,
135             String[] contents) {
136         File dir = new File(dirPath);
137 
138         if (dir.isDirectory()) {
139             File[] files = dir.listFiles();
140             if (fileNames == null) {
141                 if (files.length != 0) {
142                     fail(dirPath + " must be empty");
143                 } else {
144                     return;
145                 }
146             }
147 
148             if (files.length != fileNames.length) {
149                 fail(dirPath + " contains " + files.length + " instead of " + fileNames.length
150                         + " files");
151             }
152 
153             for (int i = 0; i < fileNames.length; i++) {
154                 String fileName = fileNames[i];
155                 boolean match = false;
156                 File file = null;
157                 for (int j = 0; j < files.length; j++) {
158                     file = files[j];
159                     if (file.getName().equals(fileName)) {
160                         match = true;
161                         break;
162                     }
163                 }
164                 if (!match) {
165                     fail(dirPath + " does not contain required " + fileName);
166                 }
167 
168                 String content = null;
169                 if (contents != null && i < contents.length) {
170                     content = contents[i];
171                 }
172                 if (content != null && !compare(file, content)) {
173                     fail("Contents of " + fileName + " in " + dirPath
174                             + " does not contain required content '" + content + "'");
175                 }
176             }
177 
178         } else {
179             fail(dirPath + " is not directoy");
180         }
181     }
182 
183     private static boolean compare(FileInputStream stream, byte[] bytes) {
184         int read;
185         int count = 0;
186         try {
187             while ((read = stream.read()) != -1) {
188                 if (bytes[count++] != read) {
189                     return false;
190                 }
191             }
192         } catch (IOException e) {
193             return false;
194         }
195         return true;
196     }
197 
198     private static boolean compare(File file, String content) {
199         FileInputStream stream = null;
200         try {
201             byte[] bytes = content.getBytes(ENCODING);
202             stream = new FileInputStream(file);
203             return compare(stream, bytes);
204         } catch (Throwable t) {
205             return false;
206         } finally {
207             if (stream != null) {
208                 try {
209                     stream.close();
210                 } catch (IOException e) {
211                 }
212             }
213         }
214     }
215 
216     @Test
217     public void basic() {
218         reset();
219         TxFileResourceManager manager = new TxFileResourceManager("TxFileManager", rootPath);
220         FileResourceUndoManager um;
221         try {
222             um = new MemoryUndoManager(tmpDir);
223             manager.setUndoManager(um);
224             manager.startTransaction(60, TimeUnit.SECONDS);
225             FileResource file = manager.getResource(rootPath + "/aha");
226             if (!file.exists()) {
227                 file.createAsFile();
228             }
229             OutputStream os = file.writeStream(true);
230             PrintStream ps = new PrintStream(os);
231             ps.println("Huhu");
232             manager.commitTransaction();
233         } catch (Throwable throwable) {
234             System.err.println(throwable);
235             manager.rollbackTransaction();
236         }
237 
238     }
239 
240     @Test
241     public void rollback() {
242         reset();
243         TxFileResourceManager manager = new TxFileResourceManager("TxFileManager", rootPath);
244         FileResourceUndoManager um;
245         try {
246             um = new MemoryUndoManager(tmpDir);
247             manager.setUndoManager(um);
248             manager.startTransaction(60, TimeUnit.SECONDS);
249             FileResource file = manager.getResource(rootPath + "/aha");
250             if (!file.exists()) {
251                 file.createAsFile();
252             }
253             OutputStream os = file.writeStream(true);
254             PrintStream ps = new PrintStream(os);
255             ps.print("Huhu");
256             os.close();
257         } catch (Throwable throwable) {
258             System.err.println(throwable);
259         } finally {
260             checkExactlyContains(rootPath, new String[] { "aha" }, new String[] { "Huhu" });
261             manager.rollbackTransaction();
262             checkIsEmpty(rootPath);
263         }
264 
265     }
266 
267     @Test
268     public void global() throws Throwable {
269         reset();
270         createInitialFiles();
271 
272         final TxFileResourceManager rm = new TxFileResourceManager("TxFileManager", rootPath);
273         FileResourceUndoManager um;
274         um = new MemoryUndoManager(tmpDir);
275         rm.setUndoManager(um);
276 
277         final RendezvousBarrier startBarrier = new RendezvousBarrier("Start2", BARRIER_TIMEOUT);
278 
279         Thread create = new Thread(new Runnable() {
280             public void run() {
281                 try {
282                     rm.startTransaction(60, TimeUnit.MINUTES);
283 
284                     rm.getResource(rootPath + "/olli/Hubert4").createAsFile();
285                     startBarrier.call();
286                     rm.getResource(rootPath + "/olli/Hubert5").createAsFile();
287                     msg = "Greetings from " + Thread.currentThread().getName() + "\n";
288                     OutputStream out = rm.getResource(rootPath + "/olli/Hubert6")
289                             .writeStream(false);
290                     out.write(msg.getBytes(ENCODING));
291 
292                     checkExactlyContains(rootPath + "/olli", new String[] { "Hubert", "Hubert4",
293                             "Hubert5", "Hubert6" }, new String[] { "", "", "", msg });
294 
295                     rm.commitTransaction();
296 
297                 } catch (Throwable e) {
298                     System.err.println("Error: " + e);
299                     e.printStackTrace();
300                 }
301             }
302         }, "Create Thread");
303 
304         Thread modify = new Thread(new Runnable() {
305             public void run() {
306                 try {
307 
308                     rm.startTransaction(60, TimeUnit.MINUTES);
309 
310                     startBarrier.meet();
311 
312                     rm.getResource(rootPath + "/olli/Hubert4").exists();
313 
314                     rm.getResource(rootPath + "/olli/Hubert1").createAsFile();
315                     rm.getResource(rootPath + "/olli/Hubert2").createAsFile();
316                     rm.getResource(rootPath + "/olli/Hubert3").createAsFile();
317 
318                     checkExactlyContains(rootPath + "/olli", new String[] { "Hubert", "Hubert1",
319                             "Hubert2", "Hubert3", "Hubert4", "Hubert5", "Hubert6" });
320 
321                     rm.getResource(rootPath + "/olli/Hubert").delete();
322                     boolean failed = false;
323                     try {
324                         rm.getResource(rootPath + "/olli/Hubert1").createAsFile();
325                     } catch (ResourceException e) {
326                         // we must not create a resource that already exists
327                         failed = true;
328                     }
329                     assertTrue(failed);
330                     rm.getResource(rootPath + "/olli/Hubert1").delete();
331                     rm.getResource(rootPath + "/olli/Hubert2").delete();
332                     rm.getResource(rootPath + "/olli/Hubert3").delete();
333                     rm.getResource(rootPath + "/olli/Hubert4").delete();
334                     rm.getResource(rootPath + "/olli/Hubert5").delete();
335 
336                     checkExactlyContains(rootPath + "/olli", new String[] { "Hubert6" });
337 
338                     rm.commitTransaction();
339                 } catch (Throwable e) {
340                     System.err.println("Error: " + e);
341                     e.printStackTrace();
342                 }
343             }
344         }, "Modify Thread");
345 
346         create.start();
347         modify.start();
348 
349         modify.join();
350         create.join();
351 
352         checkExactlyContains(rootPath + "/olli", new String[] { "Hubert6" },
353                 new String[] { msg });
354     }
355 
356 }