001: /*
002: * Copyright Aduna (http://www.aduna-software.com/) (c) 2008.
003: *
004: * Licensed under the Aduna BSD-style license.
005: */
006: package org.openrdf.sail.rdbms.managers;
007:
008: import java.sql.Connection;
009: import java.sql.DatabaseMetaData;
010: import java.sql.ResultSet;
011: import java.sql.SQLException;
012: import java.util.ArrayList;
013: import java.util.Collection;
014: import java.util.HashMap;
015: import java.util.HashSet;
016: import java.util.Iterator;
017: import java.util.LinkedList;
018: import java.util.Map;
019: import java.util.Set;
020: import java.util.Map.Entry;
021: import java.util.regex.Matcher;
022: import java.util.regex.Pattern;
023:
024: import org.slf4j.Logger;
025: import org.slf4j.LoggerFactory;
026:
027: import org.openrdf.sail.rdbms.schema.IdSequence;
028: import org.openrdf.sail.rdbms.schema.TripleTable;
029: import org.openrdf.sail.rdbms.schema.ValueTableFactory;
030:
031: /**
032: * Manages and delegates to the collection of {@link TripleTable}.
033: *
034: * @author James Leigh
035: *
036: */
037: public class TripleTableManager {
038: private static final String DEFAULT_TABLE_PREFIX = "TRIPLES";
039: private static final String OTHER_TRIPLES_TABLE = "TRIPLES";
040: public static int MAX_TABLES = Integer.MAX_VALUE;//1000;
041: public static final boolean INDEX_TRIPLES = true;
042: public Number OTHER_PRED;
043: private BNodeManager bnodes;
044: private boolean closed;
045: private Connection conn;
046: private ValueTableFactory factory;
047: private Thread initThread;
048: private LiteralManager literals;
049: private Logger logger = LoggerFactory
050: .getLogger(TripleTableManager.class);
051: private PredicateManager predicates;
052: private LinkedList<TripleTable> queue = new LinkedList<TripleTable>();
053: private Pattern tablePrefix = Pattern.compile("\\W(\\w*)\\W*$");
054: private Map<Number, TripleTable> tables = new HashMap<Number, TripleTable>();
055: private UriManager uris;
056: private HashManager hashes;
057: private int maxTables = MAX_TABLES;
058: private boolean indexingTriples = INDEX_TRIPLES;
059: private IdSequence ids;
060: Exception exc;
061:
062: public TripleTableManager(ValueTableFactory factory) {
063: this .factory = factory;
064: }
065:
066: public void setConnection(Connection conn) {
067: this .conn = conn;
068: }
069:
070: public void setIdSequence(IdSequence ids) {
071: this .ids = ids;
072: this .OTHER_PRED = ids.idOf(Long.valueOf(-1));
073: }
074:
075: public void setPredicateManager(PredicateManager predicates) {
076: this .predicates = predicates;
077: }
078:
079: public void setBNodeManager(BNodeManager bnodeTable) {
080: this .bnodes = bnodeTable;
081: }
082:
083: public void setLiteralManager(LiteralManager literalTable) {
084: this .literals = literalTable;
085: }
086:
087: public void setUriManager(UriManager uriTable) {
088: this .uris = uriTable;
089: }
090:
091: public void setHashManager(HashManager hashes) {
092: this .hashes = hashes;
093: }
094:
095: public int getMaxNumberOfTripleTables() {
096: if (maxTables == Integer.MAX_VALUE)
097: return 0;
098: return maxTables + 1;
099: }
100:
101: public void setMaxNumberOfTripleTables(int max) {
102: if (max < 1) {
103: maxTables = MAX_TABLES;
104: } else {
105: maxTables = max - 1;
106: }
107: }
108:
109: public boolean isIndexingTriples() {
110: return indexingTriples;
111: }
112:
113: public void setIndexingTriples(boolean indexingTriples) {
114: this .indexingTriples = indexingTriples;
115: }
116:
117: public void initialize() throws SQLException {
118: tables.putAll(findPredicateTables());
119: initThread = new Thread(new Runnable() {
120: public void run() {
121: try {
122: initThread();
123: } catch (Exception e) {
124: exc = e;
125: logger.error(e.toString(), e);
126: }
127: }
128: }, "table-initialize");
129: initThread.start();
130: }
131:
132: public void close() throws SQLException {
133: closed = true;
134: synchronized (queue) {
135: queue.notify();
136: }
137: Iterator<Entry<Number, TripleTable>> iter;
138: iter = tables.entrySet().iterator();
139: while (iter.hasNext()) {
140: Entry<Number, TripleTable> next = iter.next();
141: TripleTable table = next.getValue();
142: if (table.isEmpty()) {
143: predicates.remove(next.getKey());
144: table.drop();
145: iter.remove();
146: }
147: table.close();
148: }
149: }
150:
151: public void createTripleIndexes() throws SQLException {
152: indexingTriples = true;
153: for (TripleTable table : tables.values()) {
154: if (!table.isIndexed()) {
155: table.createIndex();
156: }
157: }
158: }
159:
160: public void dropTripleIndexes() throws SQLException {
161: indexingTriples = false;
162: for (TripleTable table : tables.values()) {
163: if (table.isIndexed()) {
164: table.dropIndex();
165: }
166: }
167: }
168:
169: public String findTableName(Number pred) throws SQLException {
170: return getPredicateTable(pred).getNameWhenReady();
171: }
172:
173: public synchronized TripleTable getExistingTable(Number pred) {
174: if (tables.containsKey(pred))
175: return tables.get(pred);
176: return tables.get(OTHER_PRED);
177: }
178:
179: public synchronized Collection<Number> getPredicateIds() {
180: return new ArrayList<Number>(tables.keySet());
181: }
182:
183: public synchronized TripleTable getPredicateTable(Number pred)
184: throws SQLException {
185: assert pred.longValue() != 0;
186: assert pred.equals(ids.idOf(pred));
187: if (tables.containsKey(pred))
188: return tables.get(pred);
189: if (tables.containsKey(OTHER_PRED))
190: return tables.get(OTHER_PRED);
191: String tableName = getNewTableName(pred);
192: if (tables.size() >= maxTables) {
193: tableName = OTHER_TRIPLES_TABLE;
194: }
195: TripleTable table = factory.createTripleTable(conn, tableName);
196: table.setIdSequence(ids);
197: if (tables.size() >= maxTables) {
198: table.setPredColumnPresent(true);
199: initTable(table);
200: tables.put(OTHER_PRED, table);
201: } else {
202: initTable(table);
203: tables.put(pred, table);
204: }
205: return table;
206: }
207:
208: public synchronized String getTableName(Number pred)
209: throws SQLException {
210: if (tables.containsKey(pred))
211: return tables.get(pred).getNameWhenReady();
212: if (tables.containsKey(OTHER_PRED))
213: return tables.get(OTHER_PRED).getNameWhenReady();
214: return null;
215: }
216:
217: public void removed(int count, boolean locked) throws SQLException {
218: String condition = null;
219: if (locked) {
220: condition = getExpungeCondition();
221: }
222: if (hashes == null
223: || hashes.removedStatements(count, condition)) {
224: bnodes.removedStatements(count, condition);
225: uris.removedStatements(count, condition);
226: literals.removedStatements(count, condition);
227: }
228: }
229:
230: protected Set<String> findAllTables() throws SQLException {
231: Set<String> tables = new HashSet<String>();
232: DatabaseMetaData metaData = conn.getMetaData();
233: String c = null;
234: String s = null;
235: String n = null;
236: String[] TYPE_TABLE = new String[] { "TABLE" };
237: ResultSet rs = metaData.getTables(c, s, n, TYPE_TABLE);
238: try {
239: while (rs.next()) {
240: String tableName = rs.getString(3);
241: tables.add(tableName);
242: }
243: return tables;
244: } finally {
245: rs.close();
246: }
247: }
248:
249: protected Map<Number, TripleTable> findPredicateTables()
250: throws SQLException {
251: Map<Number, TripleTable> tables = new HashMap<Number, TripleTable>();
252: Set<String> names = findPredicateTableNames();
253: for (String tableName : names) {
254: TripleTable table = factory.createTripleTable(conn,
255: tableName);
256: table.setIdSequence(ids);
257: if (tableName.equalsIgnoreCase(OTHER_TRIPLES_TABLE)) {
258: table.setPredColumnPresent(true);
259: }
260: if (indexingTriples && !table.isIndexed()) {
261: table.createIndex();
262: }
263: table.reload();
264: tables.put(key(tableName), table);
265: }
266: return tables;
267: }
268:
269: protected Set<String> findTablesWithColumn(String column)
270: throws SQLException {
271: Set<String> tables = findTablesWithExactColumn(column
272: .toUpperCase());
273: if (tables.isEmpty())
274: return findTablesWithExactColumn(column.toLowerCase());
275: return tables;
276: }
277:
278: protected Set<String> findTablesWithExactColumn(String column)
279: throws SQLException {
280: Set<String> tables = new HashSet<String>();
281: DatabaseMetaData metaData = conn.getMetaData();
282: String c = null;
283: String s = null;
284: String n = null;
285: ResultSet rs = metaData.getColumns(c, s, n, column);
286: try {
287: while (rs.next()) {
288: String tableName = rs.getString(3);
289: tables.add(tableName);
290: }
291: return tables;
292: } finally {
293: rs.close();
294: }
295: }
296:
297: protected synchronized String getExpungeCondition()
298: throws SQLException {
299: StringBuilder sb = new StringBuilder(1024);
300: for (Map.Entry<Number, TripleTable> e : tables.entrySet()) {
301: sb.append("\nAND id != ").append(e.getKey());
302: if (e.getValue().isEmpty())
303: continue;
304: sb.append(" AND NOT EXISTS (SELECT * FROM ");
305: sb.append(e.getValue().getNameWhenReady());
306: sb.append(" WHERE ctx = id OR subj = id OR obj = id");
307: if (e.getValue().isPredColumnPresent()) {
308: sb.append(" OR pred = id");
309: }
310: sb.append(")");
311: }
312: return sb.toString();
313: }
314:
315: protected String getNewTableName(Number pred) throws SQLException {
316: String prefix = getTableNamePrefix(pred);
317: String tableName = prefix + "_" + pred;
318: return tableName;
319: }
320:
321: protected Number key(String tn) {
322: if (tn.equalsIgnoreCase(OTHER_TRIPLES_TABLE))
323: return OTHER_PRED;
324: Number id = ids.idOf(Long.valueOf(tn.substring(tn
325: .lastIndexOf('_') + 1)));
326: assert id.longValue() != 0;
327: return id;
328: }
329:
330: protected String getTableNamePrefix(Number pred)
331: throws SQLException {
332: String uri = predicates.getPredicateUri(pred);
333: if (uri == null)
334: return DEFAULT_TABLE_PREFIX;
335: Matcher m = tablePrefix.matcher(uri);
336: if (!m.find())
337: return DEFAULT_TABLE_PREFIX;
338: String localName = m.group(1).replaceAll("^[^a-zA-Z]*", "");
339: if (localName.length() == 0)
340: return DEFAULT_TABLE_PREFIX;
341: if (localName.length() > 16)
342: return localName.substring(0, 16);
343: return localName;
344: }
345:
346: void initThread() throws SQLException, InterruptedException {
347: logger.debug("Starting helper thread {}", initThread.getName());
348: while (!closed) {
349: TripleTable table = null;
350: synchronized (queue) {
351: if (queue.isEmpty()) {
352: queue.wait();
353: }
354: if (!queue.isEmpty()) {
355: table = queue.removeFirst();
356: }
357: }
358: if (table != null) {
359: table.initTable();
360: table = null;
361: }
362: }
363: logger.debug("Closing helper thread {}", initThread.getName());
364: }
365:
366: private Set<String> findPredicateTableNames() throws SQLException {
367: Set<String> names = findAllTables();
368: names.retainAll(findTablesWithColumn("ctx"));
369: names.retainAll(findTablesWithColumn("subj"));
370: names.retainAll(findTablesWithColumn("obj"));
371: return names;
372: }
373:
374: private void initTable(TripleTable table) throws SQLException {
375: if (exc != null)
376: throwException();
377: table.setIndexed(indexingTriples);
378: if (true || queue == null) {
379: table.initTable();
380: } else {
381: synchronized (queue) {
382: queue.add(table);
383: queue.notify();
384: }
385: }
386: }
387:
388: private void throwException() throws SQLException {
389: if (exc instanceof SQLException) {
390: SQLException e = (SQLException) exc;
391: exc = null;
392: throw e;
393: } else if (exc instanceof RuntimeException) {
394: RuntimeException e = (RuntimeException) exc;
395: exc = null;
396: throw e;
397: }
398: }
399: }
|