001: /*
002: * (c) Copyright 2002, 2003, 2004, 2005, 2006, 2007, 2008 Hewlett-Packard Development Company, LP
003: * All rights reserved.
004: *
005: */
006:
007: //=======================================================================
008: // Package
009: package com.hp.hpl.jena.db.impl;
010:
011: //=======================================================================
012: // Imports
013: import java.sql.*;
014: import java.util.*;
015: import java.io.*;
016:
017: import com.hp.hpl.jena.db.*;
018: import com.hp.hpl.jena.shared.JenaException;
019: import com.hp.hpl.jena.util.CollectionFactory;
020:
021: import org.apache.commons.logging.Log;
022: import org.apache.commons.logging.LogFactory;
023:
024: //=======================================================================
025: /**
026: * Stores a set of sql statements loaded from a resource file.
027: * Caches prepared versions of the statements for a given db connection.
028: * <p>
029: * The resource file is located on the classpath and has the format:
030: * <pre>
031: * # comment at start of line
032: * operationName1
033: * sql code line 1
034: * ...
035: * sql code last line
036: *
037: * operationName2
038: * ...
039: * </pre>
040: * where the blank lines delimit one sql block from the next.
041: * <p>The sql code is typically a single SQL statement but some operations,
042: * specifically database initialization and cleanup may require a variable number
043: * of statments. To cater for this terminate each statement in those groups with
044: * the string ";;". Note that a single ";" is not used because these compound
045: * statements are often stored procedure definitions which end to have ";" line
046: * terminators!
047: *
048: * @author <a href="mailto:der@hplb.hpl.hp.com">Dave Reynolds</a>. Updated by hkuno to support GraphRDB.
049: * @version $Revision: 1.18 $ on $Date: 2008/01/02 12:08:23 $
050: */
051:
052: public class SQLCache {
053:
054: //=======================================================================
055: // Variables
056:
057: /** Set of sql statements indexed by operation name. */
058: protected Properties m_sql;
059:
060: /** Cache of prepared versions of the statements. Each map entry is a list
061: * of copies of the prepared statement for multi-threaded apps. */
062: protected Map m_preparedStatements = CollectionFactory
063: .createHashedMap();
064:
065: /** Track which cached, prepared statements are in use and the corresponding
066: * list to which the statement should be returned. */
067: protected Map m_cachedStmtInUse = CollectionFactory
068: .createHashedMap();
069:
070: /** the packaged jdbc connection to the database itself. */
071: protected IDBConnection m_connection;
072:
073: /** Maximum number of pre-prepared statements to keep for each operator. */
074: protected static final int MAX_PS_CACHE = 4;
075:
076: /** Set to true to enable cache of pre-prepared statements. */
077: protected boolean CACHE_PREPARED_STATEMENTS = true;
078:
079: static protected Log logger = LogFactory.getLog(SQLCache.class);
080:
081: //=======================================================================
082: // Public interface
083:
084: /**
085: * Constructor. Creates a new cache sql statements for interfacing to
086: * a specific database.
087: * @param sqlFile the name of the file of sql statements to load, this is
088: * loaded from the classpath.
089: * @param defaultOps Properties table which provides the default
090: * sql statements, any definitions of a given operation in the loaded file
091: * will override the default.
092: * @param connection the jdbc connection to the database itself
093: * @param idType the sql string to use for id types (substitutes for $id in files)
094: */
095: public SQLCache(String sqlFile, Properties defaultOps,
096: IDBConnection connection, String idType) throws IOException {
097: m_sql = loadSQLFile(sqlFile, defaultOps, idType);
098: m_connection = connection;
099: }
100:
101: /**
102: * Set to true to enable cache of pre-prepared statements.
103: */
104: public void setCachePreparedStatements(boolean state) {
105: CACHE_PREPARED_STATEMENTS = state;
106: }
107:
108: /**
109: * Return true if cache of pre-prepared statements is enabled.
110: */
111: public boolean getCachePreparedStatements() {
112: return CACHE_PREPARED_STATEMENTS;
113: }
114:
115: /**
116: * Flush the cache of all currently prepared statements.
117: */
118: public void flushPreparedStatementCache() throws RDFRDBException {
119: try {
120: Iterator it = m_preparedStatements.values().iterator();
121: while (it.hasNext()) {
122: Iterator psit = ((List) it.next()).iterator();
123: while (psit.hasNext()) {
124: ((PreparedStatement) psit.next()).close();
125: }
126: }
127: } catch (SQLException e) {
128: throw new RDFRDBException("Problem flushing PS cache", e);
129: } finally {
130: m_preparedStatements = CollectionFactory.createHashedMap();
131: m_cachedStmtInUse = CollectionFactory.createHashedMap();
132: }
133: }
134:
135: /**
136: * Return the associated jdbc connection.
137: */
138: public Connection getConnection() throws SQLException {
139: return m_connection.getConnection();
140: }
141:
142: /**
143: * Set the associated jdbc connection.
144: */
145: public void setConnection(IDBConnection connection) {
146: m_connection = connection;
147: }
148:
149: /**
150: * Return the raw SQL statement corresponding to the named operation.
151: */
152: public String getSQLStatement(String opname) throws SQLException {
153: return getSQLStatement(opname, (String[]) null);
154: }
155:
156: /**
157: * Return the raw SQL statement corresponding to the named operation.
158: * Substitute the ${a} attribute macro for the current attribute number.
159: */
160: public String getSQLStatement(String opname, String[] attr)
161: throws SQLException {
162: String cmd = m_sql.getProperty(opname);
163: if (cmd == null) {
164: if (opname.startsWith("*")) {
165: cmd = genSQLStatement(opname);
166: m_sql.setProperty(opname, cmd);
167: } else {
168: logger.error("Unable to find SQL for operation: "
169: + opname);
170: throw new SQLException(
171: "Unable to find SQL for operation: " + opname);
172: }
173: }
174: int attrCnt = (attr == null) ? 0 : attr.length;
175: if (attrCnt > 0)
176: cmd = substitute(cmd, "${a}", attr[0]);
177: if (attrCnt > 1)
178: cmd = substitute(cmd, "${b}", attr[1]);
179: if (attrCnt > 2)
180: cmd = substitute(cmd, "${c}", attr[2]);
181: if (attrCnt > 3)
182: throw new JenaException("Too many arguments");
183:
184: return cmd;
185: }
186:
187: public String getSQLStatement(String opname, String attr)
188: throws SQLException {
189: String[] param = { attr };
190: return getSQLStatement(opname, param);
191: }
192:
193: /**
194: * Return the raw SQL statement corresponding to the named operation.
195: * Attribute version - substitute the ${a} attribute macro for
196: * the current attribute number.
197: */
198: public String getSQLStatement(String opname, String attrA,
199: String attrB) throws SQLException {
200: String[] param = { attrA, attrB };
201: return getSQLStatement(opname, param);
202: }
203:
204: /**
205: * Return a set of raw SQL statements corresponding to the named operation.
206: * This is used for compound operations where more than one SQL command is needed to
207: * implement the operation (e.g. database formating and clean up). The
208: * individual statements should be separated by double-semicolons at the end of the line.
209: * <p>Needs refactoring to clarify what operations are and are not compound but for now
210: * it is assumed the caller knows which is correct. Compound statements are not called
211: * repeatedly so don't currently cache the parsed statement set.
212: */
213: public Collection getSQLStatementGroup(String opname)
214: throws SQLException {
215: String statementSrc = m_sql.getProperty(opname);
216: if (statementSrc == null) {
217: throw new SQLException("Unable to find SQL for operation: "
218: + opname);
219: }
220: int start = 0;
221: int split = 0;
222: List statements = new LinkedList();
223: while (split != -1) {
224: split = statementSrc.indexOf(";;\n", start);
225: String statement = null;
226: if (split == -1) {
227: statement = statementSrc.substring(start);
228: } else {
229: statement = statementSrc.substring(start, split);
230: start = split + 2;
231: }
232: if (!statement.trim().equals(""))
233: statements.add(statement);
234: }
235: return statements;
236: }
237:
238: /**
239: * Return a prepared SQL statement corresponding to the named operation.
240: * The statement should either be closed after use or returned to the
241: * prepared statement pool using {@link #returnPreparedSQLStatement returnPreparedSQLStatement}
242: *
243: * <p>Only works for single statements, not compound statements.
244: * @param con the jdbc connection to use for preparing statements
245: * @param opname the name of the sql operation to locate
246: * @return a prepared SQL statement appropriate for the JDBC connection
247: * used when this SQLCache was constructed or null if there is no such
248: * operation or no such connection
249: *
250: *
251: */
252:
253: public synchronized PreparedStatement getPreparedSQLStatement(
254: String opname, String[] attr) throws SQLException {
255: /* TODO extended calling format or statement format to support different
256: * result sets and conconcurrency modes.
257: */
258: PreparedStatement ps = null;
259: if (m_connection == null || opname == null)
260: return null;
261: int attrCnt = (attr == null) ? 0 : attr.length;
262: String aop = opname;
263: if (attrCnt > 0)
264: aop = concatOpName(aop, attr[0]);
265: if (attrCnt > 1)
266: aop = concatOpName(aop, attr[1]);
267: if (attrCnt > 2)
268: aop = concatOpName(aop, attr[2]);
269: if (attrCnt > 3)
270: throw new JenaException("Too many arguments");
271:
272: List psl = (List) m_preparedStatements.get(aop);
273: // OVERRIDE: added proper PreparedStatement removal.
274: if (psl != null && !psl.isEmpty()) {
275: ps = (PreparedStatement) psl.remove(0);
276: try {
277: ps.clearParameters();
278: } catch (SQLException e) {
279: ps.close();
280: }
281: }
282: if (ps == null) {
283: String sql = getSQLStatement(opname, attr);
284: if (sql == null) {
285: throw new SQLException("No SQL defined for operation: "
286: + opname);
287: }
288: if (psl == null && CACHE_PREPARED_STATEMENTS) {
289: psl = new LinkedList();
290: m_preparedStatements.put(aop, psl);
291: }
292: ps = doPrepareSQLStatement(sql);
293: }
294: if (CACHE_PREPARED_STATEMENTS)
295: m_cachedStmtInUse.put(ps, psl);
296: return ps;
297: }
298:
299: /**
300: * Prepare a SQL statement for the given statement string.
301: *
302: * <p>Only works for single statements, not compound statements.
303: * @param stmt the sql statement to prepare.
304: * @return a prepared SQL statement appropriate for the JDBC connection
305: * used when this SQLCache was constructed or null if there is no such
306: * connection.
307: */
308:
309: private synchronized PreparedStatement doPrepareSQLStatement(
310: String sql) throws SQLException {
311: if (m_connection == null)
312: return null;
313: return getConnection().prepareStatement(sql);
314: }
315:
316: /**
317: * Return a prepared SQL statement for the given statement string.
318: * The statement should either be closed after use.
319: *
320: * <p>Only works for single statements, not compound statements.
321: * @param stmt the sql statement to prepare.
322: * @return a prepared SQL statement appropriate for the JDBC connection
323: * used when this SQLCache was constructed or null if there is no such
324: * connection.
325: */
326:
327: public synchronized PreparedStatement prepareSQLStatement(String sql)
328: throws SQLException {
329: if (m_connection == null)
330: return null;
331: return doPrepareSQLStatement(sql);
332: }
333:
334: public synchronized PreparedStatement getPreparedSQLStatement(
335: String opname) throws SQLException {
336: return getPreparedSQLStatement(opname, (String[]) null);
337: }
338:
339: /**
340: * Variant on {@link #getPreparedSQLStatement getPreparedSQLStatement} which
341: * accesses the attribute variant correspond to the given attribute suffix.
342: */
343: public synchronized PreparedStatement getPreparedSQLStatement(
344: String opname, String attr) throws SQLException {
345: String[] param = { attr };
346: return getPreparedSQLStatement(opname, param);
347: }
348:
349: /**
350: * Variant on {@link #getPreparedSQLStatement getPreparedSQLStatement} which
351: * access the attribute variant correspond to the given attribute suffix.
352: */
353: public synchronized PreparedStatement getPreparedSQLStatement(
354: String opname, String attrA, String attrB)
355: throws SQLException {
356: String[] param = { attrA, attrB };
357: return getPreparedSQLStatement(opname, param);
358: }
359:
360: /**
361: * Return a prepared statement to the statement pool for reuse by
362: * another caller. Any close problems logged rather than raising exception
363: * so that iterator close() operations can be silent so that they can meet
364: * the ClosableIterator signature.
365: */
366: public synchronized void returnPreparedSQLStatement(
367: PreparedStatement ps) {
368: if (!CACHE_PREPARED_STATEMENTS) {
369: try {
370: ps.close();
371: } catch (SQLException e) {
372: logger.warn("Problem discarded prepared statement", e);
373: }
374: return;
375: }
376: List psl = (List) m_cachedStmtInUse.get(ps);
377: if (psl != null) {
378: if (psl.size() >= MAX_PS_CACHE) {
379: try {
380: ps.close();
381: } catch (SQLException e) {
382: logger.warn("Problem discarded prepared statement",
383: e);
384: }
385: } else {
386: psl.add(ps);
387: }
388: m_cachedStmtInUse.remove(ps);
389: } else {
390: throw new JenaException(
391: "Attempt to return unused prepared statement");
392: }
393: }
394:
395: /**
396: * Execute a named pre-prepared SQL query statement taking a set of arguments and return
397: * a set of results as an iterator (probably a subclass of ResultSetIterator. Returns null
398: * if they query is an update (as opposed to an empty iterator for a true query which happens
399: * to return no answers).
400: * <p>
401: * Not sure this is a good design. Reducing this to a general interface leads to lots of clunky
402: * wrapping and unwrapping of primitive types, coercions and lack of compile-time type checking.
403: * On the other hand letting the clients do this themselves with direct jdbc calls leaves us up
404: * to the mercy of the client to correctly use returnPreparedSQLStatement and on average seems
405: * to lead to more duplication of boiler plate code. Currently the client can chose either approach.
406: * <p>
407: * The calling arguments are passed in as an array.
408: */
409: public ResultSetIterator runSQLQuery(String opname, Object[] args)
410: throws SQLException {
411: PreparedStatement ps = getPreparedSQLStatement(opname);
412: if (args != null) {
413: for (int i = 0; i < args.length; i++) {
414: ps.setObject(i + 1, args[i]);
415: }
416: }
417: return executeSQL(ps, opname, new ResultSetIterator());
418: }
419:
420: /**
421: * Variant on {@link #runSQLQuery} which
422: * access the attribute variant correspond to the given attribute suffix.
423: */
424: public ResultSetIterator runSQLQuery(String opname, String attr,
425: Object[] args) throws SQLException {
426: String aop = concatOpName(opname, attr);
427: PreparedStatement ps = getPreparedSQLStatement(aop);
428:
429: if (args != null) {
430: for (int i = 0; i < args.length; i++) {
431: ps.setObject(i + 1, args[i]);
432: }
433: }
434: return executeSQL(ps, aop, new ResultSetIterator());
435: }
436:
437: /**
438: * Variant on {@link #runSQLQuery} which
439: * access the attribute variant correspond to the given attribute suffix.
440: */
441: public ResultSetIterator runSQLQuery(String opname, String attrA,
442: String attrB, Object[] args) throws SQLException {
443: String aop = concatOpName(opname, attrA, attrB);
444: PreparedStatement ps = getPreparedSQLStatement(aop);
445:
446: if (args != null) {
447: for (int i = 0; i < args.length; i++) {
448: ps.setObject(i + 1, args[i]);
449: }
450: }
451: return executeSQL(ps, aop, new ResultSetIterator());
452: }
453:
454: /**
455: * Execute a named pre-prepared SQL update statement taking a set of arguments and returning
456: * the update count.
457: */
458: public int runSQLUpdate(String opname, Object[] args)
459: throws SQLException {
460: PreparedStatement ps = getPreparedSQLStatement(opname);
461: if (args != null) {
462: for (int i = 0; i < args.length; i++) {
463: ps.setObject(i + 1, args[i]);
464: }
465: }
466: int result = ps.executeUpdate();
467: returnPreparedSQLStatement(ps);
468: return result;
469: }
470:
471: /**
472: * Variant on {@link #runSQLUpdate} which
473: * access the attribute variant correspond to the given attribute suffix.
474: */
475: public int runSQLUpdate(String opname, String attrA, Object[] args)
476: throws SQLException {
477: String aop = concatOpName(opname, attrA);
478: PreparedStatement ps = getPreparedSQLStatement(aop);
479: if (args != null) {
480: for (int i = 0; i < args.length; i++) {
481: ps.setObject(i + 1, args[i]);
482: }
483: }
484: int result = ps.executeUpdate();
485: returnPreparedSQLStatement(ps);
486: return result;
487: }
488:
489: /**
490: * Variant on {@link #runSQLUpdate} which
491: * access the attribute variant correspond to the given attribute suffix.
492: */
493: public int runSQLUpdate(String opname, String attrA, String attrB,
494: Object[] args) throws SQLException {
495: String aop = concatOpName(opname, attrA, attrB);
496: PreparedStatement ps = getPreparedSQLStatement(aop);
497: if (args != null) {
498: for (int i = 0; i < args.length; i++) {
499: ps.setObject(i + 1, args[i]);
500: }
501: }
502: int result = ps.executeUpdate();
503: returnPreparedSQLStatement(ps);
504: return result;
505: }
506:
507: /**
508: * Execute a named pre-prepared SQL query statement taking a set of arguments and return
509: * a set of results as an iterator (probably a subclass of ResultSetIterator. Returns null
510: * if they query is an update (as opposed to an empty iterator for a true query which happens
511: * to return no answers).
512: * <p>
513: * Not sure this is a good design. Reducing this to a general interface leads to lots of clunky
514: * wrapping and unwrapping of primitive types, coercions and lack of compile-time type checking.
515: * On the other hand letting the clients do this themselves with direct jdbc calls leaves us up
516: * to the mercy of the client to correctly use returnPreparedSQLStatement and on average seems
517: * to lead to more duplication of boiler plate code. Currently the client can chose either approach.
518: * <p>
519: * @param opname the name of the SQL operation to perform
520: * @param args the arguments to pass to the SQL operation as an array of Objects
521: * @param iterator the iterator to use to return the results
522: */
523: public ResultSetIterator runSQLQuery(String opname, Object[] args,
524: ResultSetIterator iterator) throws SQLException {
525: PreparedStatement ps = getPreparedSQLStatement(opname);
526: if (args != null) {
527: for (int i = 0; i < args.length; i++) {
528: ps.setObject(i + 1, args[i]);
529: }
530: }
531: return executeSQL(ps, opname, iterator);
532: }
533:
534: /**
535: * Variant on {@link #runSQLQuery} which
536: * access the attribute variant correspond to the given attribute suffix.
537: */
538: public ResultSetIterator runSQLQuery(String opname, String attrA,
539: Object[] args, ResultSetIterator iterator)
540: throws SQLException {
541: String aop = concatOpName(opname, attrA);
542: PreparedStatement ps = getPreparedSQLStatement(aop);
543: if (args != null) {
544: for (int i = 0; i < args.length; i++) {
545: ps.setObject(i + 1, args[i]);
546: }
547: }
548:
549: return executeSQL(ps, aop, iterator);
550: }
551:
552: /**
553: * Variant on {@link #runSQLQuery} which
554: * access the attribute variant correspond to the given attribute suffix.
555: */
556: public ResultSetIterator runSQLQuery(String opname, String attrA,
557: String attrB, Object[] args, ResultSetIterator iterator)
558: throws SQLException {
559: String aop = concatOpName(opname, attrA, attrB);
560: PreparedStatement ps = getPreparedSQLStatement(aop);
561: if (args != null) {
562: for (int i = 0; i < args.length; i++) {
563: ps.setObject(i + 1, args[i]);
564: }
565: }
566:
567: return executeSQL(ps, aop, iterator);
568: }
569:
570: /**
571: * Run a group of sql statements - normally used for db formating and clean up.
572: * All statements are executed even if one raises an error then the error is
573: * reported at the end.
574: *
575: * Attribute version -- substitute the ${a} attribute macro
576: * for the current attribute
577: */
578: public void runSQLGroup(String opname, String[] attr)
579: throws SQLException {
580: String op = null;
581: SQLException eignore = null;
582: String operror = null;
583: java.sql.Statement sql = getConnection().createStatement();
584: Iterator ops = getSQLStatementGroup(opname).iterator();
585: int attrCnt = attr == null ? 0 : attr.length;
586: if (attrCnt > 6)
587: throw new RDFRDBException("Too many parameters");
588: while (ops.hasNext()) {
589: op = (String) ops.next();
590: if (attrCnt > 0)
591: op = substitute(op, "${a}", attr[0]);
592: if (attrCnt > 1)
593: op = substitute(op, "${b}", attr[1]);
594: if (attrCnt > 2)
595: op = substitute(op, "${c}", attr[2]);
596: if (attrCnt > 3)
597: op = substitute(op, "${d}", attr[3]);
598: if (attrCnt > 4)
599: op = substitute(op, "${e}", attr[4]);
600: if (attrCnt > 5)
601: op = substitute(op, "${f}", attr[5]);
602: try {
603: sql.execute(op);
604: } catch (SQLException e) {
605: // This is debugging legacy, exception is still reported at the end
606: // System.out.println("Exec failure: " + op + ": " + e);
607: operror = op;
608: eignore = e;
609: }
610: }
611: sql.close();
612: if (eignore != null) {
613: // operror records the failed operator, mostly internal debugging use
614: throw eignore;
615: }
616: }
617:
618: /**
619: * Run a group of sql statements - normally used for db formating and clean up.
620: * All statements are executed even if one raises an error then the error is
621: * reported at the end.
622: */
623: public void runSQLGroup(String opname) throws SQLException {
624: runSQLGroup(opname, (String[]) null);
625: }
626:
627: /**
628: * Run a group of sql statements - normally used for db formating and clean up.
629: * All statements are executed even if one raises an error then the error is
630: * reported at the end.
631: *
632: * Attribute version -- substitute the ${a} attribute macro
633: * for the current attribute
634: */
635: public void runSQLGroup(String opname, String attr)
636: throws SQLException {
637: String[] param = { attr };
638: runSQLGroup(opname, param);
639: }
640:
641: /**
642: * Run a group of sql statements - normally used for db formating and clean up.
643: * All statements are executed even if one raises an error then the error is
644: * reported at the end.
645: *
646: * Attribute version -- substitute the ${a} attribute macro
647: * for the current attribute
648: */
649: public void runSQLGroup(String opname, String attrA, String attrB)
650: throws SQLException {
651: String[] param = { attrA, attrB };
652: runSQLGroup(opname, param);
653: }
654:
655: /**
656: * Close all prepared statements
657: */
658: public void close() throws SQLException {
659: Iterator it = m_preparedStatements.values().iterator();
660: while (it.hasNext()) {
661: List psl = (List) it.next();
662: Iterator itl = psl.iterator();
663: while (itl.hasNext()) {
664: PreparedStatement ps = (PreparedStatement) itl.next();
665: ps.close();
666: }
667: it.remove();
668: }
669: it = m_cachedStmtInUse.values().iterator();
670: while (it.hasNext()) {
671: it.remove();
672: }
673: }
674:
675: /**
676: * Load in a defined set of sql statements - see class comment for format.
677: * The loaded file is return as a Property table. This call is static
678: * to support the loading of a default sql mapping.
679: * @param sqlFile the name of the file of sql statements to load, this is
680: * loaded from the classpath.
681: * @param defaultOps a Properties table of default sql definitions.
682: * @param idType the sql string to use for id types (substitutes for $id in files)
683: */
684: public static Properties loadSQLFile(String sqlFile,
685: Properties defaultOps, String idType) throws IOException {
686: Properties sqlTable = new Properties(defaultOps);
687: BufferedReader src = openResourceFile(sqlFile);
688: String line = null;
689: while ((line = src.readLine()) != null) {
690: if (line.startsWith("#")) {
691: continue; // Comment line so skip it
692: }
693: String opName = line.trim();
694: StringBuffer sql = new StringBuffer();
695: while (true) {
696: line = src.readLine();
697: if (line == null || line.trim().equals("")) {
698: // Blank line terminates sql block
699: sqlTable.setProperty(opName, sql.toString());
700: break;
701: } else if (line.startsWith("#")) {
702: continue;
703: } else {
704: sql
705: .append(substitute(line.trim(), "${id}",
706: idType));
707: sql.append("\n");
708: }
709: }
710: if (line == null)
711: break; // Check if read to end of file
712: }
713: return sqlTable;
714: }
715:
716: /** Helper function calculate op name given substitutions */
717: public static String concatOpName(String opName, String attr) {
718: return (opName + attr);
719: }
720:
721: /** Helper function calculate op name given substitutions */
722: public static String concatOpName(String opName, String attrA,
723: String attrB) {
724: return (opName + attrA + attrB);
725: }
726:
727: /** Helper function substitute all occurances of macro with subs */
728: public static String substitute(String line, String macro,
729: String subs) {
730: int loc = line.indexOf(macro);
731: if (loc != -1) {
732: return line.substring(0, loc)
733: + subs
734: + substitute(line.substring(loc + macro.length()),
735: macro, subs);
736: } else {
737: return line;
738: }
739: }
740:
741: //=======================================================================
742: // Internal support
743:
744: /**
745: * Accessor. Returns the Properties table which maps operation names to
746: * the plain text sql statements. This is using internally in the constructor.
747: */
748: protected Properties getSQLTable() {
749: return m_sql;
750: }
751:
752: /**
753: * Open a resource file for reading. The file is found on the classpath.
754: */
755: public static BufferedReader openResourceFile(String filename)
756: throws IOException {
757: InputStream is = SQLCache.class.getClassLoader()
758: .getResourceAsStream(filename);
759: if (is == null)
760: throw new IOException("Can't open resource " + filename);
761: return new BufferedReader(new InputStreamReader(is, "US-ASCII"));
762: }
763:
764: /**
765: * Execute the given statement, return null if the statement appears to be
766: * just an update or return an iterator for the result set if the statement appears
767: * to be a query
768: */
769: protected ResultSetIterator executeSQL(PreparedStatement ps,
770: String opname, ResultSetIterator iterator)
771: throws SQLException {
772: if (ps.execute()) {
773: ResultSet rs = ps.getResultSet();
774: iterator.reset(rs, ps, this , opname);
775: return iterator;
776: } else {
777: returnPreparedSQLStatement(ps);
778: return null;
779: }
780: }
781:
782: /**
783: * Return dynamically generated SQL for the specified operation.
784: * @param opname the command to generate; must start with "*", the opname and then op params.
785: * @return the generated command as a String.
786: */
787:
788: protected String genSQLStatement(String opname) throws SQLException {
789: /* for testing. for now, we only generate one operation, findReif,
790: * to find reified statements from a triple match pattern.
791: */
792: String sql = "";
793: boolean badop = false;
794: if (opname.startsWith("*")) {
795: // a space separate the operation name from its parameters.
796: int delim = opname.indexOf(' ');
797: String op = opname.substring(1, delim);
798: String args = opname.substring(delim + 1);
799: if (op.equals("findReif")) {
800: sql = genSQLStmtFindReif(op, args);
801: } else
802: badop = true;
803: } else
804: badop = true;
805: if (badop) {
806: logger.error("Unable to generate SQL for operation: "
807: + opname);
808: throw new JenaException(
809: "Unable to generate SQL for operation: " + opname);
810: }
811: return sql;
812: }
813:
814: /**
815: * Return generate SQL for finding reified statements from a triple pattern.
816: * @param op the command to generate. should be findReif.
817: * @param args a string describing which command to generate.
818: * it has the form [N][PS|PP|PO|PT][O[C]] where N means to search
819: * for the statement URI; Px means to search for reified subjects, properties,
820: * objects or types; O means to search for reified objects; OC means the object
821: * value is rdf:Statement.
822: */
823:
824: protected String genSQLStmtFindReif(String op, String args)
825: throws SQLException {
826: /* for a reified triple pattern <S,P,O>, there are 8 cases.
827: * 1. <-,-,-> this means retrieve all reified triples. args="".
828: * 2. <S,-,-> retrieve all reified triples for this subject. args="N".
829: * 3. <S,-,O> retrieve all reified triples for this subject and
830: * object value. args="NO" or "NOC".
831: * 4. <-,-,O> retrieve all reified triples with this object value.
832: * args="O" or "OC"
833: * 5. <-,P,-> retrieve all reified triples with this property. args="Px".
834: * property must be either rdf:subject, rdf:predicate,
835: * rdf:object, rdf:type.
836: * 6. <-,P,O> retrieve all reified triples with this property and object
837: * value. args="PxO" or "PxOC".
838: * 7. <S,P,-> retrieve all reified triples with this subject and property.
839: * args="NPx".
840: * 8. <S,P,O> retrieve all reified triples with this subject, property and
841: * object value. args="NPxO" or "NPxOC".
842: */
843:
844: String stmtStr = getSQLStatement("selectReified");
845: String qual = "";
846: IRDBDriver driver = m_connection.getDriver();
847:
848: if (args.equals("")) {
849: // case 1 <-,-,-> nothing to do.
850: } else {
851: int ix = 0;
852: boolean hasSubj = false;
853: boolean hasProp = false;
854: boolean hasObj = false;
855: boolean objIsStmt = false;
856: char reifProp = ' ';
857: int argLen = args.length();
858:
859: if (args.charAt(ix) == 'N') {
860: hasSubj = true;
861: ix++;
862: }
863: hasProp = (ix < argLen) && (args.charAt(ix) == 'P');
864: if (hasProp && (ix < argLen)) {
865: ix++;
866: reifProp = args.charAt(ix++);
867: }
868: hasObj = (ix < argLen) && (args.charAt(ix) == 'O');
869: if (hasObj) {
870: ix++;
871: objIsStmt = (ix < argLen) && (args.charAt(ix) == 'C');
872: }
873: if (!hasProp) {
874: if (hasSubj) {
875: // cases 2 and 3
876: qual += driver.genSQLReifQualStmt();
877: if (hasObj) {
878: // case 3 above
879: qual += " AND "
880: + driver
881: .genSQLReifQualAnyObj(objIsStmt);
882: }
883: } else {
884: // case 4 above
885: qual += driver.genSQLReifQualAnyObj(objIsStmt);
886: }
887: } else {
888: // have a reified property
889: if (hasSubj)
890: qual += driver.genSQLReifQualStmt() + " AND ";
891: qual += driver.genSQLReifQualObj(reifProp, hasObj);
892: }
893: stmtStr += " AND " + qual;
894: }
895: return stmtStr;
896: }
897:
898: }
899:
900: /*
901: * (c) Copyright 2002, 2003, 2004, 2005, 2006, 2007, 2008 Hewlett-Packard Development Company, LP
902: * All rights reserved.
903: *
904: * Redistribution and use in source and binary forms, with or without
905: * modification, are permitted provided that the following conditions
906: * are met:
907: * 1. Redistributions of source code must retain the above copyright
908: * notice, this list of conditions and the following disclaimer.
909: * 2. Redistributions in binary form must reproduce the above copyright
910: * notice, this list of conditions and the following disclaimer in the
911: * documentation and/or other materials provided with the distribution.
912: * 3. The name of the author may not be used to endorse or promote products
913: * derived from this software without specific prior written permission.
914:
915: * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
916: * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
917: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
918: * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
919: * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
920: * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
921: * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
922: * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
923: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
924: * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
925: */
|