Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

CustomLinkResolver.java 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. /*
  2. Copyright (c) 2017 James Ahlborn
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package com.healthmarketscience.jackcess.util;
  14. import java.io.Closeable;
  15. import java.io.File;
  16. import java.io.IOException;
  17. import java.io.RandomAccessFile;
  18. import java.nio.channels.FileChannel;
  19. import java.util.Random;
  20. import com.healthmarketscience.jackcess.Database;
  21. import com.healthmarketscience.jackcess.Database.FileFormat;
  22. import com.healthmarketscience.jackcess.Table;
  23. import com.healthmarketscience.jackcess.impl.ByteUtil;
  24. import com.healthmarketscience.jackcess.impl.DatabaseImpl;
  25. import com.healthmarketscience.jackcess.impl.TableImpl;
  26. /**
  27. * Utility base implementaton of LinkResolver which facilitates loading linked
  28. * tables from files which are not access databases. The LinkResolver API
  29. * ultimately presents linked table information to the primary database using
  30. * the jackcess {@link Database} and {@link Table} classes. In order to
  31. * consume linked tables in non-mdb files, they need to somehow be coerced
  32. * into the appropriate form. The approach taken by this utility is to make
  33. * it easy to copy the external tables into a temporary mdb file for
  34. * consumption by the primary database.
  35. * <p>
  36. * The primary features of this utility:
  37. * <ul>
  38. * <li>Supports custom behavior for non-mdb files and default behavior for mdb
  39. * files, see {@link #loadCustomFile}</li>
  40. * <li>Temp db can be an actual file or entirely in memory</li>
  41. * <li>Linked tables are loaded on-demand, see {@link #loadCustomTable}</li>
  42. * <li>Temp db files will be automatically deleted on close</li>
  43. * </ul>
  44. *
  45. * @author James Ahlborn
  46. * @usage _intermediate_class_
  47. */
  48. public abstract class CustomLinkResolver implements LinkResolver
  49. {
  50. private static final Random DB_ID = new Random();
  51. private static final String MEM_DB_PREFIX = "memdb_";
  52. private static final String FILE_DB_PREFIX = "linkeddb_";
  53. /** the default file format used for temp dbs */
  54. public static final FileFormat DEFAULT_FORMAT = FileFormat.V2000;
  55. /** temp dbs default to the filesystem, not in memory */
  56. public static final boolean DEFAULT_IN_MEMORY = false;
  57. /** temp dbs end up in the system temp dir by default */
  58. public static final File DEFAULT_TEMP_DIR = null;
  59. private final FileFormat _defaultFormat;
  60. private final boolean _defaultInMemory;
  61. private final File _defaultTempDir;
  62. /**
  63. * Creates a CustomLinkResolver using the default behavior for creating temp
  64. * dbs, see {@link #DEFAULT_FORMAT}, {@link #DEFAULT_IN_MEMORY} and
  65. * {@link #DEFAULT_TEMP_DIR}.
  66. */
  67. protected CustomLinkResolver() {
  68. this(DEFAULT_FORMAT, DEFAULT_IN_MEMORY, DEFAULT_TEMP_DIR);
  69. }
  70. /**
  71. * Creates a CustomLinkResolver with the given default behavior for creating
  72. * temp dbs.
  73. *
  74. * @param defaultFormat the default format for the temp db
  75. * @param defaultInMemory whether or not the temp db should be entirely in
  76. * memory by default (while this will be faster, it
  77. * should only be used if table data is expected to
  78. * fit entirely in memory)
  79. * @param defaultTempDir the default temp dir for a file based temp db
  80. * ({@code null} for the system defaqult temp
  81. * directory)
  82. */
  83. protected CustomLinkResolver(FileFormat defaultFormat, boolean defaultInMemory,
  84. File defaultTempDir)
  85. {
  86. _defaultFormat = defaultFormat;
  87. _defaultInMemory = defaultInMemory;
  88. _defaultTempDir = defaultTempDir;
  89. }
  90. protected FileFormat getDefaultFormat() {
  91. return _defaultFormat;
  92. }
  93. protected boolean isDefaultInMemory() {
  94. return _defaultInMemory;
  95. }
  96. protected File getDefaultTempDirectory() {
  97. return _defaultTempDir;
  98. }
  99. /**
  100. * Custom implementation is:
  101. * <pre>
  102. * // attempt to load the linkeeFileName as a custom file
  103. * Object customFile = loadCustomFile(linkerDb, linkeeFileName);
  104. *
  105. * if(customFile != null) {
  106. * // this is a custom file, create and return relevant temp db
  107. * return createTempDb(customFile, getDefaultFormat(), isDefaultInMemory(),
  108. * getDefaultTempDirectory());
  109. * }
  110. *
  111. * // not a custmom file, load using the default behavior
  112. * return LinkResolver.DEFAULT.resolveLinkedDatabase(linkerDb, linkeeFileName);
  113. * </pre>
  114. *
  115. * @see #loadCustomFile
  116. * @see #createTempDb
  117. * @see LinkResolver#DEFAULT
  118. */
  119. public Database resolveLinkedDatabase(Database linkerDb, String linkeeFileName)
  120. throws IOException
  121. {
  122. Object customFile = loadCustomFile(linkerDb, linkeeFileName);
  123. if(customFile != null) {
  124. // if linker is read-only, open linkee read-only
  125. boolean readOnly = ((linkerDb instanceof DatabaseImpl) ?
  126. ((DatabaseImpl)linkerDb).isReadOnly() : false);
  127. return createTempDb(customFile, getDefaultFormat(), isDefaultInMemory(),
  128. getDefaultTempDirectory(), readOnly);
  129. }
  130. return LinkResolver.DEFAULT.resolveLinkedDatabase(linkerDb, linkeeFileName);
  131. }
  132. /**
  133. * Creates a temporary database for holding the table data from
  134. * linkeeFileName.
  135. *
  136. * @param customFile custom file state returned from {@link #loadCustomFile}
  137. * @param format the access format for the temp db
  138. * @param inMemory whether or not the temp db should be entirely in memory
  139. * (while this will be faster, it should only be used if
  140. * table data is expected to fit entirely in memory)
  141. * @param tempDir the temp dir for a file based temp db ({@code null} for
  142. * the system default temp directory)
  143. *
  144. * @return the temp db for holding the linked table info
  145. */
  146. protected Database createTempDb(Object customFile, FileFormat format,
  147. boolean inMemory, File tempDir,
  148. boolean readOnly)
  149. throws IOException
  150. {
  151. File dbFile = null;
  152. FileChannel channel = null;
  153. boolean success = false;
  154. try {
  155. if(inMemory) {
  156. dbFile = new File(MEM_DB_PREFIX + DB_ID.nextLong() +
  157. format.getFileExtension());
  158. channel = MemFileChannel.newChannel();
  159. } else {
  160. dbFile = File.createTempFile(FILE_DB_PREFIX, format.getFileExtension(),
  161. tempDir);
  162. channel = new RandomAccessFile(dbFile, DatabaseImpl.RW_CHANNEL_MODE)
  163. .getChannel();
  164. }
  165. TempDatabaseImpl.initDbChannel(channel, format);
  166. TempDatabaseImpl db = new TempDatabaseImpl(this, customFile, dbFile,
  167. channel, format, readOnly);
  168. success = true;
  169. return db;
  170. } finally {
  171. if(!success) {
  172. ByteUtil.closeQuietly(channel);
  173. deleteDbFile(dbFile);
  174. closeCustomFile(customFile);
  175. }
  176. }
  177. }
  178. private static void deleteDbFile(File dbFile) {
  179. if((dbFile != null) && (dbFile.getName().startsWith(FILE_DB_PREFIX))) {
  180. dbFile.delete();
  181. }
  182. }
  183. private static void closeCustomFile(Object customFile) {
  184. if(customFile instanceof Closeable) {
  185. ByteUtil.closeQuietly((Closeable)customFile);
  186. }
  187. }
  188. /**
  189. * Called by {@link #resolveLinkedDatabase} to determine whether the
  190. * linkeeFileName should be treated as a custom file (thus utiliziing a temp
  191. * db) or a normal access db (loaded via the default behavior). Loads any
  192. * state necessary for subsequently loading data from linkeeFileName.
  193. * <p>
  194. * The returned custom file state object will be maintained with the temp db
  195. * and passed to {@link #loadCustomTable} whenever a new table needs to be
  196. * loaded. Also, if this object is {@link Closeable}, it will be closed
  197. * with the temp db.
  198. *
  199. * @param linkerDb the primary database in which the link is defined
  200. * @param linkeeFileName the name of the linked file
  201. *
  202. * @return non-{@code null} if linkeeFileName should be treated as a custom
  203. * file (using a temp db) or {@code null} if it should be treated as
  204. * a normal access db.
  205. */
  206. protected abstract Object loadCustomFile(
  207. Database linkerDb, String linkeeFileName) throws IOException;
  208. /**
  209. * Called by an instance of a temp db when a missing table is first requested.
  210. *
  211. * @param tempDb the temp db instance which should be populated with the
  212. * relevant table info for the given tableName
  213. * @param customFile custom file state returned from {@link #loadCustomFile}
  214. * @param tableName the name of the table which is requested from the linked
  215. * file
  216. *
  217. * @return {@code true} if the table was available in the linked file,
  218. * {@code false} otherwise
  219. */
  220. protected abstract boolean loadCustomTable(
  221. Database tempDb, Object customFile, String tableName)
  222. throws IOException;
  223. /**
  224. * Subclass of DatabaseImpl which allows us to load tables "on demand" as
  225. * well as delete the temporary db on close.
  226. */
  227. private static class TempDatabaseImpl extends DatabaseImpl
  228. {
  229. private final CustomLinkResolver _resolver;
  230. private final Object _customFile;
  231. protected TempDatabaseImpl(CustomLinkResolver resolver, Object customFile,
  232. File file, FileChannel channel,
  233. FileFormat fileFormat, boolean readOnly)
  234. throws IOException
  235. {
  236. super(file, channel, true, false, fileFormat, null, null, null,
  237. readOnly);
  238. _resolver = resolver;
  239. _customFile = customFile;
  240. }
  241. @Override
  242. protected TableImpl getTable(String name, boolean includeSystemTables)
  243. throws IOException
  244. {
  245. TableImpl table = super.getTable(name, includeSystemTables);
  246. if((table == null) &&
  247. _resolver.loadCustomTable(this, _customFile, name)) {
  248. table = super.getTable(name, includeSystemTables);
  249. }
  250. return table;
  251. }
  252. @Override
  253. public void close() throws IOException {
  254. try {
  255. super.close();
  256. } finally {
  257. deleteDbFile(getFile());
  258. closeCustomFile(_customFile);
  259. }
  260. }
  261. static FileChannel initDbChannel(FileChannel channel, FileFormat format)
  262. throws IOException
  263. {
  264. FileFormatDetails details = getFileFormatDetails(format);
  265. transferDbFrom(channel, getResourceAsStream(details.getEmptyFilePath()));
  266. return channel;
  267. }
  268. }
  269. }