1 | /* |
2 | * Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0, |
3 | * and the EPL 1.0 (http://h2database.com/html/license.html). |
4 | * Initial Developer: H2 Group |
5 | */ |
6 | package org.h2.store.fs; |
7 | |
8 | import java.io.IOException; |
9 | import java.io.OutputStream; |
10 | import java.nio.ByteBuffer; |
11 | import java.nio.channels.FileChannel; |
12 | import java.nio.channels.FileLock; |
13 | |
14 | /** |
15 | * A file system that records all write operations and can re-play them. |
16 | */ |
17 | public class FilePathRec extends FilePathWrapper { |
18 | |
19 | private static final FilePathRec INSTANCE = new FilePathRec(); |
20 | |
21 | private static Recorder recorder; |
22 | |
23 | private boolean trace; |
24 | |
25 | /** |
26 | * Register the file system. |
27 | */ |
28 | public static void register() { |
29 | FilePath.register(INSTANCE); |
30 | } |
31 | |
32 | /** |
33 | * Set the recorder class. |
34 | * |
35 | * @param recorder the recorder |
36 | */ |
37 | public static void setRecorder(Recorder recorder) { |
38 | FilePathRec.recorder = recorder; |
39 | } |
40 | |
41 | @Override |
42 | public boolean createFile() { |
43 | log(Recorder.CREATE_NEW_FILE, name); |
44 | return super.createFile(); |
45 | } |
46 | |
47 | @Override |
48 | public FilePath createTempFile(String suffix, boolean deleteOnExit, |
49 | boolean inTempDir) throws IOException { |
50 | log(Recorder.CREATE_TEMP_FILE, unwrap(name) + ":" + suffix + ":" + |
51 | deleteOnExit + ":" + inTempDir); |
52 | return super.createTempFile(suffix, deleteOnExit, inTempDir); |
53 | } |
54 | |
55 | @Override |
56 | public void delete() { |
57 | log(Recorder.DELETE, name); |
58 | super.delete(); |
59 | } |
60 | |
61 | @Override |
62 | public FileChannel open(String mode) throws IOException { |
63 | return new FileRec(this, super.open(mode), name); |
64 | } |
65 | |
66 | @Override |
67 | public OutputStream newOutputStream(boolean append) throws IOException { |
68 | log(Recorder.OPEN_OUTPUT_STREAM, name); |
69 | return super.newOutputStream(append); |
70 | } |
71 | |
72 | @Override |
73 | public void moveTo(FilePath newPath, boolean atomicReplace) { |
74 | log(Recorder.RENAME, unwrap(name) + ":" + unwrap(newPath.name)); |
75 | super.moveTo(newPath, atomicReplace); |
76 | } |
77 | |
78 | public boolean isTrace() { |
79 | return trace; |
80 | } |
81 | |
82 | public void setTrace(boolean trace) { |
83 | this.trace = trace; |
84 | } |
85 | |
86 | /** |
87 | * Log the operation. |
88 | * |
89 | * @param op the operation |
90 | * @param fileName the file name(s) |
91 | */ |
92 | void log(int op, String fileName) { |
93 | log(op, fileName, null, 0); |
94 | } |
95 | |
96 | /** |
97 | * Log the operation. |
98 | * |
99 | * @param op the operation |
100 | * @param fileName the file name |
101 | * @param data the data or null |
102 | * @param x the value or 0 |
103 | */ |
104 | void log(int op, String fileName, byte[] data, long x) { |
105 | if (recorder != null) { |
106 | recorder.log(op, fileName, data, x); |
107 | } |
108 | } |
109 | |
110 | /** |
111 | * Get the prefix for this file system. |
112 | * |
113 | * @return the prefix |
114 | */ |
115 | @Override |
116 | public String getScheme() { |
117 | return "rec"; |
118 | } |
119 | |
120 | } |
121 | |
122 | /** |
123 | * A file object that records all write operations and can re-play them. |
124 | */ |
125 | class FileRec extends FileBase { |
126 | |
127 | private final FilePathRec rec; |
128 | private final FileChannel channel; |
129 | private final String name; |
130 | |
131 | FileRec(FilePathRec rec, FileChannel file, String fileName) { |
132 | this.rec = rec; |
133 | this.channel = file; |
134 | this.name = fileName; |
135 | } |
136 | |
137 | @Override |
138 | public void implCloseChannel() throws IOException { |
139 | channel.close(); |
140 | } |
141 | |
142 | @Override |
143 | public long position() throws IOException { |
144 | return channel.position(); |
145 | } |
146 | |
147 | @Override |
148 | public long size() throws IOException { |
149 | return channel.size(); |
150 | } |
151 | |
152 | @Override |
153 | public int read(ByteBuffer dst) throws IOException { |
154 | return channel.read(dst); |
155 | } |
156 | |
157 | @Override |
158 | public int read(ByteBuffer dst, long position) throws IOException { |
159 | return channel.read(dst, position); |
160 | } |
161 | |
162 | @Override |
163 | public FileChannel position(long pos) throws IOException { |
164 | channel.position(pos); |
165 | return this; |
166 | } |
167 | |
168 | @Override |
169 | public FileChannel truncate(long newLength) throws IOException { |
170 | rec.log(Recorder.TRUNCATE, name, null, newLength); |
171 | channel.truncate(newLength); |
172 | return this; |
173 | } |
174 | |
175 | @Override |
176 | public void force(boolean metaData) throws IOException { |
177 | channel.force(metaData); |
178 | } |
179 | |
180 | @Override |
181 | public int write(ByteBuffer src) throws IOException { |
182 | byte[] buff = src.array(); |
183 | int len = src.remaining(); |
184 | if (src.position() != 0 || len != buff.length) { |
185 | byte[] b = new byte[len]; |
186 | System.arraycopy(buff, src.arrayOffset() + src.position(), b, 0, len); |
187 | buff = b; |
188 | } |
189 | int result = channel.write(src); |
190 | rec.log(Recorder.WRITE, name, buff, channel.position()); |
191 | return result; |
192 | } |
193 | |
194 | @Override |
195 | public int write(ByteBuffer src, long position) throws IOException { |
196 | byte[] buff = src.array(); |
197 | int len = src.remaining(); |
198 | if (src.position() != 0 || len != buff.length) { |
199 | byte[] b = new byte[len]; |
200 | System.arraycopy(buff, src.arrayOffset() + src.position(), b, 0, len); |
201 | buff = b; |
202 | } |
203 | int result = channel.write(src, position); |
204 | rec.log(Recorder.WRITE, name, buff, position); |
205 | return result; |
206 | } |
207 | |
208 | @Override |
209 | public synchronized FileLock tryLock(long position, long size, |
210 | boolean shared) throws IOException { |
211 | return channel.tryLock(position, size, shared); |
212 | } |
213 | |
214 | @Override |
215 | public String toString() { |
216 | return name; |
217 | } |
218 | |
219 | } |