Source Code Cross Referenced for Database.java in  » Database-Client » Jackcess » com » healthmarketscience » jackcess » Java Source Code / Java DocumentationJava Source Code and Java Documentation

Java Source Code / Java Documentation
1. 6.0 JDK Core
2. 6.0 JDK Modules
3. 6.0 JDK Modules com.sun
4. 6.0 JDK Modules com.sun.java
5. 6.0 JDK Modules sun
6. 6.0 JDK Platform
7. Ajax
8. Apache Harmony Java SE
9. Aspect oriented
10. Authentication Authorization
11. Blogger System
12. Build
13. Byte Code
14. Cache
15. Chart
16. Chat
17. Code Analyzer
18. Collaboration
19. Content Management System
20. Database Client
21. Database DBMS
22. Database JDBC Connection Pool
23. Database ORM
24. Development
25. EJB Server geronimo
26. EJB Server GlassFish
27. EJB Server JBoss 4.2.1
28. EJB Server resin 3.1.5
29. ERP CRM Financial
30. ESB
31. Forum
32. GIS
33. Graphic Library
34. Groupware
35. HTML Parser
36. IDE
37. IDE Eclipse
38. IDE Netbeans
39. Installer
40. Internationalization Localization
41. Inversion of Control
42. Issue Tracking
43. J2EE
44. JBoss
45. JMS
46. JMX
47. Library
48. Mail Clients
49. Net
50. Parser
51. PDF
52. Portal
53. Profiler
54. Project Management
55. Report
56. RSS RDF
57. Rule Engine
58. Science
59. Scripting
60. Search Engine
61. Security
62. Sevlet Container
63. Source Control
64. Swing Library
65. Template Engine
66. Test Coverage
67. Testing
68. UML
69. Web Crawler
70. Web Framework
71. Web Mail
72. Web Server
73. Web Services
74. Web Services apache cxf 2.0.1
75. Web Services AXIS2
76. Wiki Engine
77. Workflow Engines
78. XML
79. XML UI
Java
Java Tutorial
Java Open Source
Jar File Download
Java Articles
Java Products
Java by API
Photoshop Tutorials
Maya Tutorials
Flash Tutorials
3ds-Max Tutorials
Illustrator Tutorials
GIMP Tutorials
C# / C Sharp
C# / CSharp Tutorial
C# / CSharp Open Source
ASP.Net
ASP.NET Tutorial
JavaScript DHTML
JavaScript Tutorial
JavaScript Reference
HTML / CSS
HTML CSS Reference
C / ANSI-C
C Tutorial
C++
C++ Tutorial
Ruby
PHP
Python
Python Tutorial
Python Open Source
SQL Server / T-SQL
SQL Server / T-SQL Tutorial
Oracle PL / SQL
Oracle PL/SQL Tutorial
PostgreSQL
SQL / MySQL
MySQL Tutorial
VB.Net
VB.Net Tutorial
Flash / Flex / ActionScript
VBA / Excel / Access / Word
XML
XML Tutorial
Microsoft Office PowerPoint 2007 Tutorial
Microsoft Office Excel 2007 Tutorial
Microsoft Office Word 2007 Tutorial
Java Source Code / Java Documentation » Database Client » Jackcess » com.healthmarketscience.jackcess 
Source Cross Referenced  Class Diagram Java Document (Java Doc) 


001:        /*
002:        Copyright (c) 2005 Health Market Science, Inc.
003:
004:        This library is free software; you can redistribute it and/or
005:        modify it under the terms of the GNU Lesser General Public
006:        License as published by the Free Software Foundation; either
007:        version 2.1 of the License, or (at your option) any later version.
008:
009:        This library is distributed in the hope that it will be useful,
010:        but WITHOUT ANY WARRANTY; without even the implied warranty of
011:        MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
012:        Lesser General Public License for more details.
013:
014:        You should have received a copy of the GNU Lesser General Public
015:        License along with this library; if not, write to the Free Software
016:        Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
017:        USA
018:
019:        You can contact Health Market Science at info@healthmarketscience.com
020:        or at the following address:
021:
022:        Health Market Science
023:        2700 Horizon Drive
024:        Suite 200
025:        King of Prussia, PA 19406
026:         */
027:
028:        package com.healthmarketscience.jackcess;
029:
030:        import java.io.BufferedReader;
031:        import java.io.Closeable;
032:        import java.io.File;
033:        import java.io.FileNotFoundException;
034:        import java.io.FileReader;
035:        import java.io.Flushable;
036:        import java.io.IOException;
037:        import java.io.RandomAccessFile;
038:        import java.nio.ByteBuffer;
039:        import java.nio.channels.Channels;
040:        import java.nio.channels.FileChannel;
041:        import java.sql.ResultSet;
042:        import java.sql.ResultSetMetaData;
043:        import java.sql.SQLException;
044:        import java.util.ArrayList;
045:        import java.util.Arrays;
046:        import java.util.Collection;
047:        import java.util.ConcurrentModificationException;
048:        import java.util.Date;
049:        import java.util.HashMap;
050:        import java.util.HashSet;
051:        import java.util.Iterator;
052:        import java.util.LinkedList;
053:        import java.util.List;
054:        import java.util.Map;
055:        import java.util.NoSuchElementException;
056:        import java.util.Set;
057:
058:        import org.apache.commons.lang.builder.ToStringBuilder;
059:        import org.apache.commons.logging.Log;
060:        import org.apache.commons.logging.LogFactory;
061:
062:        /**
063:         * An Access database.
064:         *
065:         * @author Tim McCune
066:         */
067:        public class Database implements  Iterable<Table>, Closeable, Flushable {
068:
069:            private static final Log LOG = LogFactory.getLog(Database.class);
070:
071:            /** this is the default "userId" used if we cannot find existing info.  this
072:                seems to be some standard "Admin" userId for access files */
073:            private static final byte[] SYS_DEFAULT_SID = new byte[2];
074:            static {
075:                SYS_DEFAULT_SID[0] = (byte) 0xA6;
076:                SYS_DEFAULT_SID[1] = (byte) 0x33;
077:            }
078:
079:            /** default value for the auto-sync value ({@code true}).  this is slower,
080:                but leaves more chance of a useable database in the face of failures. */
081:            public static final boolean DEFAULT_AUTO_SYNC = true;
082:
083:            /** Batch commit size for copying other result sets into this database */
084:            private static final int COPY_TABLE_BATCH_SIZE = 200;
085:
086:            /** System catalog always lives on page 2 */
087:            private static final int PAGE_SYSTEM_CATALOG = 2;
088:            /** Name of the system catalog */
089:            private static final String TABLE_SYSTEM_CATALOG = "MSysObjects";
090:
091:            /** this is the access control bit field for created tables.  the value used
092:                is equivalent to full access (Visual Basic DAO PermissionEnum constant:
093:                dbSecFullAccess) */
094:            private static final Integer SYS_FULL_ACCESS_ACM = 1048575;
095:
096:            /** ACE table column name of the actual access control entry */
097:            private static final String ACE_COL_ACM = "ACM";
098:            /** ACE table column name of the inheritable attributes flag */
099:            private static final String ACE_COL_F_INHERITABLE = "FInheritable";
100:            /** ACE table column name of the relevant objectId */
101:            private static final String ACE_COL_OBJECT_ID = "ObjectId";
102:            /** ACE table column name of the relevant userId */
103:            private static final String ACE_COL_SID = "SID";
104:
105:            /** Relationship table column name of the column count */
106:            private static final String REL_COL_COLUMN_COUNT = "ccolumn";
107:            /** Relationship table column name of the flags */
108:            private static final String REL_COL_FLAGS = "grbit";
109:            /** Relationship table column name of the index of the columns */
110:            private static final String REL_COL_COLUMN_INDEX = "icolumn";
111:            /** Relationship table column name of the "to" column name */
112:            private static final String REL_COL_TO_COLUMN = "szColumn";
113:            /** Relationship table column name of the "to" table name */
114:            private static final String REL_COL_TO_TABLE = "szObject";
115:            /** Relationship table column name of the "from" column name */
116:            private static final String REL_COL_FROM_COLUMN = "szReferencedColumn";
117:            /** Relationship table column name of the "from" table name */
118:            private static final String REL_COL_FROM_TABLE = "szReferencedObject";
119:            /** Relationship table column name of the relationship */
120:            private static final String REL_COL_NAME = "szRelationship";
121:
122:            /** System catalog column name of the page on which system object definitions
123:                are stored */
124:            private static final String CAT_COL_ID = "Id";
125:            /** System catalog column name of the name of a system object */
126:            private static final String CAT_COL_NAME = "Name";
127:            private static final String CAT_COL_OWNER = "Owner";
128:            /** System catalog column name of a system object's parent's id */
129:            private static final String CAT_COL_PARENT_ID = "ParentId";
130:            /** System catalog column name of the type of a system object */
131:            private static final String CAT_COL_TYPE = "Type";
132:            /** System catalog column name of the date a system object was created */
133:            private static final String CAT_COL_DATE_CREATE = "DateCreate";
134:            /** System catalog column name of the date a system object was updated */
135:            private static final String CAT_COL_DATE_UPDATE = "DateUpdate";
136:            /** System catalog column name of the flags column */
137:            private static final String CAT_COL_FLAGS = "Flags";
138:
139:            /** Empty database template for creating new databases */
140:            private static final String EMPTY_MDB = "com/healthmarketscience/jackcess/empty.mdb";
141:            /** Prefix for column or table names that are reserved words */
142:            private static final String ESCAPE_PREFIX = "x";
143:            /** Prefix that flags system tables */
144:            private static final String PREFIX_SYSTEM = "MSys";
145:            /** Name of the system object that is the parent of all tables */
146:            private static final String SYSTEM_OBJECT_NAME_TABLES = "Tables";
147:            /** Name of the table that contains system access control entries */
148:            private static final String TABLE_SYSTEM_ACES = "MSysACEs";
149:            /** Name of the table that contains table relationships */
150:            private static final String TABLE_SYSTEM_RELATIONSHIPS = "MSysRelationships";
151:            /** System object type for table definitions */
152:            private static final Short TYPE_TABLE = (short) 1;
153:
154:            /** the columns to read when reading system catalog initially */
155:            private static Collection<String> SYSTEM_CATALOG_COLUMNS = new HashSet<String>(
156:                    Arrays.asList(CAT_COL_NAME, CAT_COL_TYPE, CAT_COL_ID));
157:
158:            /**
159:             * All of the reserved words in Access that should be escaped when creating
160:             * table or column names
161:             */
162:            private static final Set<String> RESERVED_WORDS = new HashSet<String>();
163:            static {
164:                //Yup, there's a lot.
165:                RESERVED_WORDS.addAll(Arrays.asList("add", "all",
166:                        "alphanumeric", "alter", "and", "any", "application",
167:                        "as", "asc", "assistant", "autoincrement", "avg",
168:                        "between", "binary", "bit", "boolean", "by", "byte",
169:                        "char", "character", "column", "compactdatabase",
170:                        "constraint", "container", "count", "counter",
171:                        "create", "createdatabase", "createfield",
172:                        "creategroup", "createindex", "createobject",
173:                        "createproperty", "createrelation", "createtabledef",
174:                        "createuser", "createworkspace", "currency",
175:                        "currentuser", "database", "date", "datetime",
176:                        "delete", "desc", "description", "disallow",
177:                        "distinct", "distinctrow", "document", "double",
178:                        "drop", "echo", "else", "end", "eqv", "error",
179:                        "exists", "exit", "false", "field", "fields",
180:                        "fillcache", "float", "float4", "float8", "foreign",
181:                        "form", "forms", "from", "full", "function", "general",
182:                        "getobject", "getoption", "gotopage", "group",
183:                        "group by", "guid", "having", "idle", "ieeedouble",
184:                        "ieeesingle", "if", "ignore", "imp", "in", "index",
185:                        "indexes", "inner", "insert", "inserttext", "int",
186:                        "integer", "integer1", "integer2", "integer4", "into",
187:                        "is", "join", "key", "lastmodified", "left", "level",
188:                        "like", "logical", "logical1", "long", "longbinary",
189:                        "longtext", "macro", "match", "max", "min", "mod",
190:                        "memo", "module", "money", "move", "name",
191:                        "newpassword", "no", "not", "null", "number",
192:                        "numeric", "object", "oleobject", "off", "on",
193:                        "openrecordset", "option", "or", "order", "outer",
194:                        "owneraccess", "parameter", "parameters", "partial",
195:                        "percent", "pivot", "primary", "procedure", "property",
196:                        "queries", "query", "quit", "real", "recalc",
197:                        "recordset", "references", "refresh", "refreshlink",
198:                        "registerdatabase", "relation", "repaint",
199:                        "repairdatabase", "report", "reports", "requery",
200:                        "right", "screen", "section", "select", "set",
201:                        "setfocus", "setoption", "short", "single", "smallint",
202:                        "some", "sql", "stdev", "stdevp", "string", "sum",
203:                        "table", "tabledef", "tabledefs", "tableid", "text",
204:                        "time", "timestamp", "top", "transform", "true",
205:                        "type", "union", "unique", "update", "user", "value",
206:                        "values", "var", "varp", "varbinary", "varchar",
207:                        "where", "with", "workspace", "xor", "year", "yes",
208:                        "yesno"));
209:            }
210:
211:            /** Buffer to hold database pages */
212:            private ByteBuffer _buffer;
213:            /** ID of the Tables system object */
214:            private Integer _tableParentId;
215:            /** Format that the containing database is in */
216:            private final JetFormat _format;
217:            /**
218:             * Map of UPPERCASE table names to page numbers containing their definition
219:             * and their stored table name.
220:             */
221:            private Map<String, TableInfo> _tableLookup = new HashMap<String, TableInfo>();
222:            /** set of table names as stored in the mdb file, created on demand */
223:            private Set<String> _tableNames;
224:            /** Reads and writes database pages */
225:            private final PageChannel _pageChannel;
226:            /** System catalog table */
227:            private Table _systemCatalog;
228:            /** System access control entries table */
229:            private Table _accessControlEntries;
230:            /** page number of the system relationships table */
231:            private Integer _relationshipsPageNumber;
232:            /** System relationships table (initialized on first use) */
233:            private Table _relationships;
234:            /** SIDs to use for the ACEs added for new tables */
235:            private final List<byte[]> _newTableSIDs = new ArrayList<byte[]>();
236:
237:            /**
238:             * Open an existing Database.  If the existing file is not writeable, the
239:             * file will be opened read-only.  Auto-syncing is enabled for the returned
240:             * Database.
241:             * @param mdbFile File containing the database
242:             */
243:            public static Database open(File mdbFile) throws IOException {
244:                return open(mdbFile, false);
245:            }
246:
247:            /**
248:             * Open an existing Database.  If the existing file is not writeable or the
249:             * readOnly flag is <code>true</code>, the file will be opened read-only.
250:             * Auto-syncing is enabled for the returned Database.
251:             * @param mdbFile File containing the database
252:             * @param readOnly iff <code>true</code>, force opening file in read-only
253:             *                 mode
254:             */
255:            public static Database open(File mdbFile, boolean readOnly)
256:                    throws IOException {
257:                return open(mdbFile, readOnly, DEFAULT_AUTO_SYNC);
258:            }
259:
260:            /**
261:             * Open an existing Database.  If the existing file is not writeable or the
262:             * readOnly flag is <code>true</code>, the file will be opened read-only.
263:             * @param mdbFile File containing the database
264:             * @param readOnly iff <code>true</code>, force opening file in read-only
265:             *                 mode
266:             * @param autoSync whether or not to enable auto-syncing on write.  if
267:             *                 {@code true}, writes will be immediately flushed to disk.
268:             *                 This leaves the database in a (fairly) consistent state
269:             *                 on each write, but can be very inefficient for many
270:             *                 updates.  if {@code false}, flushing to disk happens at
271:             *                 the jvm's leisure, which can be much faster, but may
272:             *                 leave the database in an inconsistent state if failures
273:             *                 are encountered during writing.
274:             */
275:            public static Database open(File mdbFile, boolean readOnly,
276:                    boolean autoSync) throws IOException {
277:                if (!mdbFile.exists() || !mdbFile.canRead()) {
278:                    throw new FileNotFoundException(
279:                            "given file does not exist: " + mdbFile);
280:                }
281:                return new Database(openChannel(mdbFile,
282:                        (!mdbFile.canWrite() || readOnly)), autoSync);
283:            }
284:
285:            /**
286:             * Create a new Database
287:             * @param mdbFile Location to write the new database to.  <b>If this file
288:             *    already exists, it will be overwritten.</b>
289:             */
290:            public static Database create(File mdbFile) throws IOException {
291:                return create(mdbFile, DEFAULT_AUTO_SYNC);
292:            }
293:
294:            /**
295:             * Create a new Database
296:             * @param mdbFile Location to write the new database to.  <b>If this file
297:             *    already exists, it will be overwritten.</b>
298:             * @param autoSync whether or not to enable auto-syncing on write.  if
299:             *                 {@code true}, writes will be immediately flushed to disk.
300:             *                 This leaves the database in a (fairly) consistent state
301:             *                 on each write, but can be very inefficient for many
302:             *                 updates.  if {@code false}, flushing to disk happens at
303:             *                 the jvm's leisure, which can be much faster, but may
304:             *                 leave the database in an inconsistent state if failures
305:             *                 are encountered during writing.
306:             */
307:            public static Database create(File mdbFile, boolean autoSync)
308:                    throws IOException {
309:                FileChannel channel = openChannel(mdbFile, false);
310:                channel.truncate(0);
311:                channel.transferFrom(Channels
312:                        .newChannel(Thread.currentThread()
313:                                .getContextClassLoader().getResourceAsStream(
314:                                        EMPTY_MDB)), 0, Integer.MAX_VALUE);
315:                return new Database(channel, autoSync);
316:            }
317:
318:            private static FileChannel openChannel(File mdbFile,
319:                    boolean readOnly) throws FileNotFoundException {
320:                String mode = (readOnly ? "r" : "rw");
321:                return new RandomAccessFile(mdbFile, mode).getChannel();
322:            }
323:
324:            /**
325:             * Create a new database by reading it in from a FileChannel.
326:             * @param channel File channel of the database.  This needs to be a
327:             *    FileChannel instead of a ReadableByteChannel because we need to
328:             *    randomly jump around to various points in the file.
329:             */
330:            protected Database(FileChannel channel, boolean autoSync)
331:                    throws IOException {
332:                _format = JetFormat.getFormat(channel);
333:                _pageChannel = new PageChannel(channel, _format, autoSync);
334:                // note, it's slighly sketchy to pass ourselves along partially
335:                // constructed, but only our _format and _pageChannel refs should be
336:                // needed
337:                _pageChannel.initialize(this );
338:                _buffer = _pageChannel.createPageBuffer();
339:                readSystemCatalog();
340:            }
341:
342:            public PageChannel getPageChannel() {
343:                return _pageChannel;
344:            }
345:
346:            public JetFormat getFormat() {
347:                return _format;
348:            }
349:
350:            /**
351:             * @return The system catalog table
352:             */
353:            public Table getSystemCatalog() {
354:                return _systemCatalog;
355:            }
356:
357:            /**
358:             * @return The system Access Control Entries table
359:             */
360:            public Table getAccessControlEntries() {
361:                return _accessControlEntries;
362:            }
363:
364:            /**
365:             * Read the system catalog
366:             */
367:            private void readSystemCatalog() throws IOException {
368:                _systemCatalog = readTable(TABLE_SYSTEM_CATALOG,
369:                        PAGE_SYSTEM_CATALOG);
370:                for (Map<String, Object> row : Cursor.createCursor(
371:                        _systemCatalog).iterable(SYSTEM_CATALOG_COLUMNS)) {
372:                    String name = (String) row.get(CAT_COL_NAME);
373:                    if (name != null
374:                            && TYPE_TABLE.equals(row.get(CAT_COL_TYPE))) {
375:                        if (!name.startsWith(PREFIX_SYSTEM)) {
376:                            addTable((String) row.get(CAT_COL_NAME),
377:                                    (Integer) row.get(CAT_COL_ID));
378:                        } else if (TABLE_SYSTEM_ACES.equals(name)) {
379:                            int pageNumber = (Integer) row.get(CAT_COL_ID);
380:                            _accessControlEntries = readTable(
381:                                    TABLE_SYSTEM_ACES, pageNumber);
382:                        } else if (TABLE_SYSTEM_RELATIONSHIPS.equals(name)) {
383:                            _relationshipsPageNumber = (Integer) row
384:                                    .get(CAT_COL_ID);
385:                        }
386:                    } else if (SYSTEM_OBJECT_NAME_TABLES.equals(name)) {
387:                        _tableParentId = (Integer) row.get(CAT_COL_ID);
388:                    }
389:                }
390:
391:                // check for required system values
392:                if (_accessControlEntries == null) {
393:                    throw new IOException("Did not find required "
394:                            + TABLE_SYSTEM_ACES + " table");
395:                }
396:                if (_tableParentId == null) {
397:                    throw new IOException(
398:                            "Did not find required parent table id");
399:                }
400:
401:                if (LOG.isDebugEnabled()) {
402:                    LOG.debug("Finished reading system catalog.  Tables: "
403:                            + getTableNames());
404:                }
405:            }
406:
407:            /**
408:             * @return The names of all of the user tables (String)
409:             */
410:            public Set<String> getTableNames() {
411:                if (_tableNames == null) {
412:                    _tableNames = new HashSet<String>();
413:                    for (TableInfo tableInfo : _tableLookup.values()) {
414:                        _tableNames.add(tableInfo.tableName);
415:                    }
416:                }
417:                return _tableNames;
418:            }
419:
420:            /**
421:             * @return an unmodifiable Iterator of the user Tables in this Database.
422:             * @throws IllegalStateException if an IOException is thrown by one of the
423:             *         operations, the actual exception will be contained within
424:             * @throws ConcurrentModificationException if a table is added to the
425:             *         database while an Iterator is in use.
426:             */
427:            public Iterator<Table> iterator() {
428:                return new TableIterator();
429:            }
430:
431:            /**
432:             * @param name Table name
433:             * @return The table, or null if it doesn't exist
434:             */
435:            public Table getTable(String name) throws IOException {
436:
437:                TableInfo tableInfo = lookupTable(name);
438:
439:                if ((tableInfo == null) || (tableInfo.pageNumber == null)) {
440:                    return null;
441:                }
442:
443:                return readTable(tableInfo.tableName, tableInfo.pageNumber);
444:            }
445:
446:            /**
447:             * Create a new table in this database
448:             * @param name Name of the table to create
449:             * @param columns List of Columns in the table
450:             */
451:            public void createTable(String name, List<Column> columns)
452:                    throws IOException {
453:                if (getTable(name) != null) {
454:                    throw new IllegalArgumentException(
455:                            "Cannot create table with name of existing table");
456:                }
457:                if (columns.isEmpty()) {
458:                    throw new IllegalArgumentException(
459:                            "Cannot create table with no columns");
460:                }
461:
462:                Set<String> colNames = new HashSet<String>();
463:                // next, validate the column definitions
464:                for (Column column : columns) {
465:                    column.validate(_format);
466:                    if (!colNames.add(column.getName().toUpperCase())) {
467:                        throw new IllegalArgumentException(
468:                                "duplicate column name: " + column.getName());
469:                    }
470:                }
471:
472:                if (Table.countAutoNumberColumns(columns) > 1) {
473:                    throw new IllegalArgumentException(
474:                            "Can have at most one AutoNumber column per table");
475:                }
476:
477:                //Write the tdef page to disk.
478:                int tdefPageNumber = Table.writeTableDefinition(columns,
479:                        _pageChannel, _format);
480:
481:                //Add this table to our internal list.
482:                addTable(name, Integer.valueOf(tdefPageNumber));
483:
484:                //Add this table to system tables
485:                addToSystemCatalog(name, tdefPageNumber);
486:                addToAccessControlEntries(tdefPageNumber);
487:            }
488:
489:            /**
490:             * Finds all the relationships in the database between the given tables.
491:             */
492:            public List<Relationship> getRelationships(Table table1,
493:                    Table table2) throws IOException {
494:                // the relationships table does not get loaded until first accessed
495:                if (_relationships == null) {
496:                    if (_relationshipsPageNumber == null) {
497:                        throw new IOException(
498:                                "Could not find system relationships table");
499:                    }
500:                    _relationships = readTable(TABLE_SYSTEM_RELATIONSHIPS,
501:                            _relationshipsPageNumber);
502:                }
503:
504:                int nameCmp = table1.getName().compareTo(table2.getName());
505:                if (nameCmp == 0) {
506:                    throw new IllegalArgumentException(
507:                            "Must provide two different tables");
508:                }
509:                if (nameCmp > 0) {
510:                    // we "order" the two tables given so that we will return a collection
511:                    // of relationships in the same order regardless of whether we are given
512:                    // (TableFoo, TableBar) or (TableBar, TableFoo).
513:                    Table tmp = table1;
514:                    table1 = table2;
515:                    table2 = tmp;
516:                }
517:
518:                List<Relationship> relationships = new ArrayList<Relationship>();
519:                Cursor cursor = createCursorWithOptionalIndex(_relationships,
520:                        REL_COL_FROM_TABLE, table1.getName());
521:                collectRelationships(cursor, table1, table2, relationships);
522:                cursor = createCursorWithOptionalIndex(_relationships,
523:                        REL_COL_TO_TABLE, table1.getName());
524:                collectRelationships(cursor, table2, table1, relationships);
525:
526:                return relationships;
527:            }
528:
529:            /**
530:             * Finds the relationships matching the given from and to tables from the
531:             * given cursor and adds them to the given list.
532:             */
533:            private void collectRelationships(Cursor cursor, Table fromTable,
534:                    Table toTable, List<Relationship> relationships) {
535:                for (Map<String, Object> row : cursor) {
536:                    String fromName = (String) row.get(REL_COL_FROM_TABLE);
537:                    String toName = (String) row.get(REL_COL_TO_TABLE);
538:
539:                    if (fromTable.getName().equals(fromName)
540:                            && toTable.getName().equals(toName)) {
541:
542:                        String relName = (String) row.get(REL_COL_NAME);
543:
544:                        // found more info for a relationship.  see if we already have some
545:                        // info for this relationship
546:                        Relationship rel = null;
547:                        for (Relationship tmp : relationships) {
548:                            if (tmp.getName().equals(relName)) {
549:                                rel = tmp;
550:                                break;
551:                            }
552:                        }
553:
554:                        if (rel == null) {
555:                            // new relationship
556:                            int numCols = (Integer) row
557:                                    .get(REL_COL_COLUMN_COUNT);
558:                            int flags = (Integer) row.get(REL_COL_FLAGS);
559:                            rel = new Relationship(relName, fromTable, toTable,
560:                                    flags, numCols);
561:                            relationships.add(rel);
562:                        }
563:
564:                        // add column info
565:                        int colIdx = (Integer) row.get(REL_COL_COLUMN_INDEX);
566:                        Column fromCol = fromTable.getColumn((String) row
567:                                .get(REL_COL_FROM_COLUMN));
568:                        Column toCol = toTable.getColumn((String) row
569:                                .get(REL_COL_TO_COLUMN));
570:
571:                        rel.getFromColumns().set(colIdx, fromCol);
572:                        rel.getToColumns().set(colIdx, toCol);
573:                    }
574:                }
575:            }
576:
577:            /**
578:             * Add a new table to the system catalog
579:             * @param name Table name
580:             * @param pageNumber Page number that contains the table definition
581:             */
582:            private void addToSystemCatalog(String name, int pageNumber)
583:                    throws IOException {
584:                Object[] catalogRow = new Object[_systemCatalog
585:                        .getColumnCount()];
586:                int idx = 0;
587:                Date creationTime = new Date();
588:                for (Iterator<Column> iter = _systemCatalog.getColumns()
589:                        .iterator(); iter.hasNext(); idx++) {
590:                    Column col = iter.next();
591:                    if (CAT_COL_ID.equals(col.getName())) {
592:                        catalogRow[idx] = Integer.valueOf(pageNumber);
593:                    } else if (CAT_COL_NAME.equals(col.getName())) {
594:                        catalogRow[idx] = name;
595:                    } else if (CAT_COL_TYPE.equals(col.getName())) {
596:                        catalogRow[idx] = TYPE_TABLE;
597:                    } else if (CAT_COL_DATE_CREATE.equals(col.getName())
598:                            || CAT_COL_DATE_UPDATE.equals(col.getName())) {
599:                        catalogRow[idx] = creationTime;
600:                    } else if (CAT_COL_PARENT_ID.equals(col.getName())) {
601:                        catalogRow[idx] = _tableParentId;
602:                    } else if (CAT_COL_FLAGS.equals(col.getName())) {
603:                        catalogRow[idx] = Integer.valueOf(0);
604:                    } else if (CAT_COL_OWNER.equals(col.getName())) {
605:                        byte[] owner = new byte[2];
606:                        catalogRow[idx] = owner;
607:                        owner[0] = (byte) 0xcf;
608:                        owner[1] = (byte) 0x5f;
609:                    }
610:                }
611:                _systemCatalog.addRow(catalogRow);
612:            }
613:
614:            /**
615:             * Add a new table to the system's access control entries
616:             * @param pageNumber Page number that contains the table definition
617:             */
618:            private void addToAccessControlEntries(int pageNumber)
619:                    throws IOException {
620:
621:                if (_newTableSIDs.isEmpty()) {
622:                    initNewTableSIDs();
623:                }
624:
625:                Column acmCol = _accessControlEntries.getColumn(ACE_COL_ACM);
626:                Column inheritCol = _accessControlEntries
627:                        .getColumn(ACE_COL_F_INHERITABLE);
628:                Column objIdCol = _accessControlEntries
629:                        .getColumn(ACE_COL_OBJECT_ID);
630:                Column sidCol = _accessControlEntries.getColumn(ACE_COL_SID);
631:
632:                // construct a collection of ACE entries mimicing those of our parent, the
633:                // "Tables" system object
634:                List<Object[]> aceRows = new ArrayList<Object[]>(_newTableSIDs
635:                        .size());
636:                for (byte[] sid : _newTableSIDs) {
637:                    Object[] aceRow = new Object[_accessControlEntries
638:                            .getColumnCount()];
639:                    aceRow[acmCol.getColumnIndex()] = SYS_FULL_ACCESS_ACM;
640:                    aceRow[inheritCol.getColumnIndex()] = Boolean.FALSE;
641:                    aceRow[objIdCol.getColumnIndex()] = Integer
642:                            .valueOf(pageNumber);
643:                    aceRow[sidCol.getColumnIndex()] = sid;
644:                    aceRows.add(aceRow);
645:                }
646:                _accessControlEntries.addRows(aceRows);
647:            }
648:
649:            /**
650:             * Determines the collection of SIDs which need to be added to new tables.
651:             */
652:            private void initNewTableSIDs() throws IOException {
653:                // search for ACEs matching the tableParentId.  use the index on the
654:                // objectId column if found (should be there)
655:                Cursor cursor = createCursorWithOptionalIndex(
656:                        _accessControlEntries, ACE_COL_OBJECT_ID,
657:                        _tableParentId);
658:
659:                for (Map<String, Object> row : cursor) {
660:                    Integer objId = (Integer) row.get(ACE_COL_OBJECT_ID);
661:                    if (_tableParentId.equals(objId)) {
662:                        _newTableSIDs.add((byte[]) row.get(ACE_COL_SID));
663:                    }
664:                }
665:
666:                if (_newTableSIDs.isEmpty()) {
667:                    // if all else fails, use the hard-coded default
668:                    _newTableSIDs.add(SYS_DEFAULT_SID);
669:                }
670:            }
671:
672:            /**
673:             * Reads a table with the given name from the given pageNumber.
674:             */
675:            private Table readTable(String name, int pageNumber)
676:                    throws IOException {
677:                _pageChannel.readPage(_buffer, pageNumber);
678:                byte pageType = _buffer.get(0);
679:                if (pageType != PageTypes.TABLE_DEF) {
680:                    throw new IOException("Looking for " + name + " at page "
681:                            + pageNumber + ", but page type is " + pageType);
682:                }
683:                return new Table(this , _buffer, pageNumber, name);
684:            }
685:
686:            /**
687:             * Creates a Cursor restricted to the given column value if possible (using
688:             * an existing index), otherwise a simple table cursor.
689:             */
690:            private static Cursor createCursorWithOptionalIndex(Table table,
691:                    String colName, Object colValue) throws IOException {
692:                try {
693:                    return new CursorBuilder(table).setIndexByColumns(
694:                            table.getColumn(colName))
695:                            .setSpecificEntry(colValue).toCursor();
696:                } catch (IllegalArgumentException e) {
697:                    LOG.info("Could not find expected index on table "
698:                            + table.getName());
699:                }
700:                // use table scan instead
701:                return Cursor.createCursor(table);
702:            }
703:
704:            /**
705:             * Copy an existing JDBC ResultSet into a new table in this database
706:             * @param name Name of the new table to create
707:             * @param source ResultSet to copy from
708:             */
709:            public void copyTable(String name, ResultSet source)
710:                    throws SQLException, IOException {
711:                copyTable(name, source, SimpleImportFilter.INSTANCE);
712:            }
713:
714:            /**
715:             * Copy an existing JDBC ResultSet into a new table in this database
716:             * @param name Name of the new table to create
717:             * @param source ResultSet to copy from
718:             * @param filter valid import filter
719:             */
720:            public void copyTable(String name, ResultSet source,
721:                    ImportFilter filter) throws SQLException, IOException {
722:                ResultSetMetaData md = source.getMetaData();
723:                List<Column> columns = new LinkedList<Column>();
724:                for (int i = 1; i <= md.getColumnCount(); i++) {
725:                    Column column = new Column();
726:                    column.setName(escape(md.getColumnName(i)));
727:                    int lengthInUnits = md.getColumnDisplaySize(i);
728:                    column.setSQLType(md.getColumnType(i), lengthInUnits);
729:                    DataType type = column.getType();
730:                    // we check for isTrueVariableLength here to avoid setting the length
731:                    // for a NUMERIC column, which pretends to be var-len, even though it
732:                    // isn't
733:                    if (type.isTrueVariableLength() && !type.isLongValue()) {
734:                        column.setLengthInUnits((short) lengthInUnits);
735:                    }
736:                    if (type.getHasScalePrecision()) {
737:                        int scale = md.getScale(i);
738:                        int precision = md.getPrecision(i);
739:                        if (type.isValidScale(scale)) {
740:                            column.setScale((byte) scale);
741:                        }
742:                        if (type.isValidPrecision(precision)) {
743:                            column.setPrecision((byte) precision);
744:                        }
745:                    }
746:                    columns.add(column);
747:                }
748:                createTable(escape(name), filter.filterColumns(columns, md));
749:                Table table = getTable(escape(name));
750:                List<Object[]> rows = new ArrayList<Object[]>(
751:                        COPY_TABLE_BATCH_SIZE);
752:                while (source.next()) {
753:                    Object[] row = new Object[md.getColumnCount()];
754:                    for (int i = 0; i < row.length; i++) {
755:                        row[i] = source.getObject(i + 1);
756:                    }
757:                    rows.add(filter.filterRow(row));
758:                    if (rows.size() == COPY_TABLE_BATCH_SIZE) {
759:                        table.addRows(rows);
760:                        rows.clear();
761:                    }
762:                }
763:                if (rows.size() > 0) {
764:                    table.addRows(rows);
765:                }
766:            }
767:
768:            /**
769:             * Copy a delimited text file into a new table in this database
770:             * @param name Name of the new table to create
771:             * @param f Source file to import
772:             * @param delim Regular expression representing the delimiter string.
773:             */
774:            public void importFile(String name, File f, String delim)
775:                    throws IOException {
776:                importFile(name, f, delim, SimpleImportFilter.INSTANCE);
777:            }
778:
779:            /**
780:             * Copy a delimited text file into a new table in this database
781:             * @param name Name of the new table to create
782:             * @param f Source file to import
783:             * @param delim Regular expression representing the delimiter string.
784:             * @param filter valid import filter
785:             */
786:            public void importFile(String name, File f, String delim,
787:                    ImportFilter filter) throws IOException {
788:                BufferedReader in = null;
789:                try {
790:                    in = new BufferedReader(new FileReader(f));
791:                    importReader(name, in, delim, filter);
792:                } finally {
793:                    if (in != null) {
794:                        try {
795:                            in.close();
796:                        } catch (IOException ex) {
797:                            LOG.warn("Could not close file "
798:                                    + f.getAbsolutePath(), ex);
799:                        }
800:                    }
801:                }
802:            }
803:
804:            /**
805:             * Copy a delimited text file into a new table in this database
806:             * @param name Name of the new table to create
807:             * @param in Source reader to import
808:             * @param delim Regular expression representing the delimiter string.
809:             */
810:            public void importReader(String name, BufferedReader in,
811:                    String delim) throws IOException {
812:                importReader(name, in, delim, SimpleImportFilter.INSTANCE);
813:            }
814:
815:            /**
816:             * Copy a delimited text file into a new table in this database
817:             * @param name Name of the new table to create
818:             * @param in Source reader to import
819:             * @param delim Regular expression representing the delimiter string.
820:             * @param filter valid import filter
821:             */
822:            public void importReader(String name, BufferedReader in,
823:                    String delim, ImportFilter filter) throws IOException {
824:                String line = in.readLine();
825:                if (line == null || line.trim().length() == 0) {
826:                    return;
827:                }
828:
829:                String tableName = escape(name);
830:                int counter = 0;
831:                while (getTable(tableName) != null) {
832:                    tableName = escape(name + (counter++));
833:                }
834:
835:                List<Column> columns = new LinkedList<Column>();
836:                String[] columnNames = line.split(delim);
837:
838:                for (int i = 0; i < columnNames.length; i++) {
839:                    columns.add(new ColumnBuilder(escape(columnNames[i]),
840:                            DataType.TEXT).setLength(
841:                            (short) DataType.TEXT.getMaxSize()).toColumn());
842:                }
843:
844:                try {
845:                    createTable(tableName, filter.filterColumns(columns, null));
846:                    Table table = getTable(tableName);
847:                    List<Object[]> rows = new ArrayList<Object[]>(
848:                            COPY_TABLE_BATCH_SIZE);
849:
850:                    while ((line = in.readLine()) != null) {
851:                        // 
852:                        // Handle the situation where the end of the line
853:                        // may have null fields.  We always want to add the
854:                        // same number of columns to the table each time.
855:                        //
856:                        Object[] data = new Object[columnNames.length];
857:                        String[] splitData = line.split(delim);
858:                        System.arraycopy(splitData, 0, data, 0,
859:                                splitData.length);
860:                        rows.add(filter.filterRow(data));
861:                        if (rows.size() == COPY_TABLE_BATCH_SIZE) {
862:                            table.addRows(rows);
863:                            rows.clear();
864:                        }
865:                    }
866:                    if (rows.size() > 0) {
867:                        table.addRows(rows);
868:                    }
869:                } catch (SQLException e) {
870:                    throw (IOException) new IOException(e.getMessage())
871:                            .initCause(e);
872:                }
873:            }
874:
875:            /**
876:             * Flushes any current changes to the database file to disk.
877:             */
878:            public void flush() throws IOException {
879:                _pageChannel.flush();
880:            }
881:
882:            /**
883:             * Close the database file
884:             */
885:            public void close() throws IOException {
886:                _pageChannel.close();
887:            }
888:
889:            /**
890:             * @return A table or column name escaped for Access
891:             */
892:            private String escape(String s) {
893:                if (isReservedWord(s)) {
894:                    return ESCAPE_PREFIX + s;
895:                }
896:                return s;
897:            }
898:
899:            /**
900:             * @return {@code true} if the given string is a reserved word,
901:             *         {@code false} otherwise
902:             */
903:            public static boolean isReservedWord(String s) {
904:                return RESERVED_WORDS.contains(s.toLowerCase());
905:            }
906:
907:            @Override
908:            public String toString() {
909:                return ToStringBuilder.reflectionToString(this );
910:            }
911:
912:            /**
913:             * Adds a table to the _tableLookup and resets the _tableNames set
914:             */
915:            private void addTable(String tableName, Integer pageNumber) {
916:                _tableLookup.put(toLookupTableName(tableName), new TableInfo(
917:                        pageNumber, tableName));
918:                // clear this, will be created next time needed
919:                _tableNames = null;
920:            }
921:
922:            /**
923:             * @returns the tableInfo of the given table, if any
924:             */
925:            private TableInfo lookupTable(String tableName) {
926:                return _tableLookup.get(toLookupTableName(tableName));
927:            }
928:
929:            /**
930:             * @return a string usable in the _tableLookup map.
931:             */
932:            private String toLookupTableName(String tableName) {
933:                return ((tableName != null) ? tableName.toUpperCase() : null);
934:            }
935:
936:            /**
937:             * Utility class for storing table page number and actual name.
938:             */
939:            private static class TableInfo {
940:                public final Integer pageNumber;
941:                public final String tableName;
942:
943:                private TableInfo(Integer newPageNumber, String newTableName) {
944:                    pageNumber = newPageNumber;
945:                    tableName = newTableName;
946:                }
947:            }
948:
949:            /**
950:             * Table iterator for this database, unmodifiable.
951:             */
952:            private class TableIterator implements  Iterator<Table> {
953:                private Iterator<String> _tableNameIter;
954:
955:                private TableIterator() {
956:                    _tableNameIter = getTableNames().iterator();
957:                }
958:
959:                public boolean hasNext() {
960:                    return _tableNameIter.hasNext();
961:                }
962:
963:                public void remove() {
964:                    throw new UnsupportedOperationException();
965:                }
966:
967:                public Table next() {
968:                    if (!hasNext()) {
969:                        throw new NoSuchElementException();
970:                    }
971:                    try {
972:                        return getTable(_tableNameIter.next());
973:                    } catch (IOException e) {
974:                        throw new IllegalStateException(e);
975:                    }
976:                }
977:            }
978:
979:        }
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.