1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package com.healthmarketscience.jackcess.util;
18
19 import java.io.Closeable;
20 import java.io.IOException;
21 import java.nio.channels.FileChannel;
22 import java.nio.file.Files;
23 import java.nio.file.Path;
24 import java.nio.file.Paths;
25 import java.util.Random;
26
27 import com.healthmarketscience.jackcess.Database;
28 import com.healthmarketscience.jackcess.Database.FileFormat;
29 import com.healthmarketscience.jackcess.Table;
30 import com.healthmarketscience.jackcess.impl.ByteUtil;
31 import com.healthmarketscience.jackcess.impl.DatabaseImpl;
32 import com.healthmarketscience.jackcess.impl.TableImpl;
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56 public abstract class CustomLinkResolver implements LinkResolver
57 {
58 private static final Random DB_ID = new Random();
59
60 private static final String MEM_DB_PREFIX = "memdb_";
61 private static final String FILE_DB_PREFIX = "linkeddb_";
62
63
64 public static final FileFormat DEFAULT_FORMAT = FileFormat.V2000;
65
66 public static final boolean DEFAULT_IN_MEMORY = false;
67
68 public static final Path DEFAULT_TEMP_DIR = null;
69
70 private final FileFormat _defaultFormat;
71 private final boolean _defaultInMemory;
72 private final Path _defaultTempDir;
73
74
75
76
77
78
79 protected CustomLinkResolver() {
80 this(DEFAULT_FORMAT, DEFAULT_IN_MEMORY, DEFAULT_TEMP_DIR);
81 }
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96 protected CustomLinkResolver(FileFormat defaultFormat, boolean defaultInMemory,
97 Path defaultTempDir)
98 {
99 _defaultFormat = defaultFormat;
100 _defaultInMemory = defaultInMemory;
101 _defaultTempDir = defaultTempDir;
102 }
103
104 protected FileFormat getDefaultFormat() {
105 return _defaultFormat;
106 }
107
108 protected boolean isDefaultInMemory() {
109 return _defaultInMemory;
110 }
111
112 protected Path getDefaultTempDirectory() {
113 return _defaultTempDir;
114 }
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136 @Override
137 public Databasem/healthmarketscience/jackcess/Database.html#Database">Database resolveLinkedDatabase(Database linkerDb, String linkeeFileName)
138 throws IOException
139 {
140 Object customFile = loadCustomFile(linkerDb, linkeeFileName);
141 if(customFile != null) {
142
143 boolean readOnly = ((linkerDb instanceof DatabaseImpl) ?
144 ((DatabaseImpl)linkerDb).isReadOnly() : false);
145 return createTempDb(customFile, getDefaultFormat(), isDefaultInMemory(),
146 getDefaultTempDirectory(), readOnly);
147 }
148 return LinkResolver.DEFAULT.resolveLinkedDatabase(linkerDb, linkeeFileName);
149 }
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165 protected Database createTempDb(Object customFile, FileFormat format,
166 boolean inMemory, Path tempDir,
167 boolean readOnly)
168 throws IOException
169 {
170 Path dbFile = null;
171 FileChannel channel = null;
172 boolean success = false;
173 try {
174
175 if(inMemory) {
176 dbFile = Paths.get(MEM_DB_PREFIX + DB_ID.nextLong() +
177 format.getFileExtension());
178 channel = MemFileChannel.newChannel();
179 } else {
180 dbFile = ((tempDir != null) ?
181 Files.createTempFile(tempDir, FILE_DB_PREFIX,
182 format.getFileExtension()) :
183 Files.createTempFile(FILE_DB_PREFIX,
184 format.getFileExtension()));
185 channel = FileChannel.open(dbFile, DatabaseImpl.RW_CHANNEL_OPTS);
186 }
187
188 TempDatabaseImpl.initDbChannel(channel, format);
189 TempDatabaseImpl db = new TempDatabaseImpl(this, customFile, dbFile,
190 channel, format, readOnly);
191 success = true;
192 return db;
193
194 } finally {
195 if(!success) {
196 ByteUtil.closeQuietly(channel);
197 deleteDbFile(dbFile);
198 closeCustomFile(customFile);
199 }
200 }
201 }
202
203 private static void deleteDbFile(Path dbFile) {
204 if((dbFile != null) &&
205 dbFile.getFileName().toString().startsWith(FILE_DB_PREFIX)) {
206 try {
207 Files.deleteIfExists(dbFile);
208 } catch(IOException ignores) {}
209 }
210 }
211
212 private static void closeCustomFile(Object customFile) {
213 if(customFile instanceof Closeable) {
214 ByteUtil.closeQuietly((Closeable)customFile);
215 }
216 }
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236 protected abstract Object loadCustomFile(
237 Database linkerDb, String linkeeFileName) throws IOException;
238
239
240
241
242
243
244
245
246
247
248
249
250
251 protected abstract boolean loadCustomTable(
252 Database tempDb, Object customFile, String tableName)
253 throws IOException;
254
255
256
257
258
259
260 private static class TempDatabaseImpl extends DatabaseImpl
261 {
262 private final CustomLinkResolver _resolver;
263 private final Object _customFile;
264
265 protected TempDatabaseImpl(CustomLinkResolver resolver, Object customFile,
266 Path file, FileChannel channel,
267 FileFormat fileFormat, boolean readOnly)
268 throws IOException
269 {
270 super(file, channel, true, false, fileFormat, null, null, null,
271 readOnly, false);
272 _resolver = resolver;
273 _customFile = customFile;
274 }
275
276 @Override
277 protected TableImpl getTable(String name, boolean includeSystemTables)
278 throws IOException
279 {
280 TableImpl table = super.getTable(name, includeSystemTables);
281 if((table == null) &&
282 _resolver.loadCustomTable(this, _customFile, name)) {
283 table = super.getTable(name, includeSystemTables);
284 }
285 return table;
286 }
287
288 @Override
289 public void close() throws IOException {
290 try {
291 super.close();
292 } finally {
293 deleteDbFile(getPath());
294 closeCustomFile(_customFile);
295 }
296 }
297
298 static FileChannel initDbChannel(FileChannel channel, FileFormat format)
299 throws IOException
300 {
301 FileFormatDetails details = getFileFormatDetails(format);
302 transferDbFrom(channel, getResourceAsStream(details.getEmptyFilePath()));
303 return channel;
304 }
305 }
306
307 }