001: /*
002: * Copyright (C) 1999-2004 <A href="http://www-ist.massey.ac.nz/JBDietrich" target="_top">Jens Dietrich</a>
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 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 USA
017: */
018: package org.mandarax.sql;
019:
020: import java.sql.*;
021: import javax.sql.DataSource;
022: import org.mandarax.util.logging.LogCategories;
023: import org.mandarax.kernel.*;
024: import org.mandarax.util.ClauseIterator;
025: import org.mandarax.util.PredicateUtils;
026:
027: /**
028: * Implementation of a predicate based on a SQL query.
029: * The query defines the structure of the predicate, SQL types are mapped via
030: * a DBMapping object to java types. The query does not have a WHERE clause,
031: * since the where clause would only define the extension (= the rows in the result set),
032: * not the structure itself. The where clause comes in when clause sets are defined
033: * on top of the predicate.
034: * <p>
035: * As from version 1.7.1, SQLPredicates can be performed (@see org.mandarax.kernel.SemanticsSupport).
036: * @see java.sql.ResultSet
037: * @see org.mandarax.sql.SQLTypeMapping
038: * @author <A href="http://www-ist.massey.ac.nz/JBDietrich" target="_top">Jens Dietrich</A>
039: * @version 3.4 <7 March 05>
040: * @since 1.5
041: */
042: public class SQLPredicate implements Predicate, LogCategories {
043:
044: private String name = null;
045: private DataSource dataSource = null;
046: private String query = null;
047: private SQLTypeMapping typeMapping = new DefaultTypeMapping();
048: private Class[] structure = null;
049: private String[] slotNames = null;
050:
051: /**
052: * Constructor.
053: */
054: public SQLPredicate() {
055: super ();
056: }
057:
058: /**
059: * Get the structure of the predicate, i.e. the types associated with the columns
060: * in the result set of the query. We obtain the information by issuing a query,
061: * therefore the database must be 'live'.
062: */
063: public void fetchStructure() throws SQLException {
064: if (dataSource != null) {
065: Connection con = dataSource.getConnection();
066: Statement stmt = con.createStatement();
067: java.sql.ResultSet rs = stmt.executeQuery(query);
068: ResultSetMetaData metaData = rs.getMetaData();
069: int cols = metaData.getColumnCount();
070: Class[] clazzes = new Class[cols];
071:
072: for (int i = 1; i <= cols; i++) {
073: try {
074: if (typeMapping == null) {
075: clazzes[i - 1] = Class.forName(metaData
076: .getColumnClassName(i));
077: } else {
078: clazzes[i - 1] = typeMapping.getType(metaData
079: .getColumnType(i), i);
080: }
081: } catch (ClassNotFoundException x) {
082: con.close();
083:
084: throw new SQLException(
085: "Result set meta data returns unknown java type "
086: + metaData.getColumnClassName(i)
087: + " at position " + i);
088: }
089: }
090:
091: structure = clazzes;
092: }
093: }
094:
095: /**
096: * Set the data source.
097: * @return a data source.
098: */
099: public DataSource getDataSource() {
100: return dataSource;
101: }
102:
103: /**
104: * Get the name of the predicate.
105: * @return the name of the predicate
106: */
107: public String getName() {
108: return name;
109: }
110:
111: /**
112: * Get the query string.
113: * @return a query
114: */
115: public String getQuery() {
116: return query;
117: }
118:
119: /**
120: * Get the type structure of the object, e.g. the types of terms
121: * that can be used with this constructor.
122: * @return an array of classes
123: */
124: public java.lang.Class[] getStructure() {
125: if (structure == null) {
126: try {
127: fetchStructure();
128: } catch (SQLException x) {
129: LOG_SQL
130: .error(
131: "Error fetching structure from meta data, perhaps the JDBC driver does not support this",
132: x);
133: }
134: }
135:
136: return structure;
137: }
138:
139: /**
140: * Set the structure of the predicates, i.e. the types of the terms.
141: * Should be handled with care since it must be consistent with the type mapping
142: * used and the (SQL) type and number of columns in the result set.
143: * @param an array of classes
144: */
145: public void setStructure(Class[] struct) {
146: structure = struct;
147: }
148:
149: /**
150: * Set the type mapping used.
151: * @param map a type mapping
152: */
153: public void setTypeMapping(SQLTypeMapping map) {
154: typeMapping = map;
155: }
156:
157: /**
158: * Get the new type mapping.
159: * @return a type mapping
160: */
161: public SQLTypeMapping getTypeMapping() {
162: return typeMapping;
163: }
164:
165: /**
166: * Perform the predicate using an array of terms as parameters.
167: * If the terms can be resolved, we iterate over the result set and look for matching records.
168: * If such a record is found, Boolean.TRUE is returned. Boolean.FALSE is returned otherwise.
169: * Note that this operation can be slow! @todo: Figure out performance improvments by adding a WHERE clause.
170: * @return the result of the perform operation
171: * @param parameter an array of terms
172: * @param session a session object
173: * @throws java.lang.UnsupportedOperationException thrown if this constructor does not have a sematics and this operation cannot be supported
174: * @throws java.lang.IllegalArgumentException
175: */
176: public Object perform(org.mandarax.kernel.Term[] parameter,
177: Session session) throws UnsupportedOperationException,
178: IllegalArgumentException {
179:
180: Object[] objects = new Object[parameter.length];
181: for (int i = 0; i < parameter.length; i++) {
182: objects[i] = parameter[i].resolve(session);
183: }
184:
185: try {
186: SQLClauseSet cs = new SQLClauseSet(this ,
187: SQLClauseSet.NO_CACHE);
188: Term[] terms = null;
189: ConstantTerm t = null;
190: int i = 0;
191: for (ClauseIterator clauses = cs.clauses(); clauses
192: .hasMoreClauses();) {
193: Fact f = (Fact) clauses.nextClause();
194: // compare
195: terms = f.getTerms();
196: boolean result = true;
197: i = 0;
198: while (result && i < terms.length) {
199: t = (ConstantTerm) terms[i];
200: if (!objects[i].equals(t.getObject()))
201: result = false;
202: i = i + 1;
203: }
204: if (result)
205: return Boolean.TRUE;
206: }
207: } catch (Exception x) {
208: LOG_SQL.debug("Exception evaluating SQL predicate", x);
209: throw new IllegalArgumentException(
210: "Cannot evaluate predicate with these set of parameters");
211: }
212: return Boolean.FALSE;
213:
214: }
215:
216: /**
217: * Set the data source.
218: * @param ds a data source.
219: */
220: public void setDataSource(DataSource ds) {
221: dataSource = ds;
222: }
223:
224: /**
225: * Set the query.
226: * @param q a query string (a SELECT statement <em>without</em> an WHERE clause
227: */
228: public void setQuery(String q) {
229: query = q;
230: }
231:
232: /**
233: * Constructor.
234: * @param n a name
235: * @param q a query
236: * @param ds a data source
237: */
238: public SQLPredicate(String n, String q, DataSource ds) {
239: super ();
240:
241: name = n;
242: query = q;
243: dataSource = ds;
244: }
245:
246: /**
247: * Constructor with a given connection manager.
248: *
249: * @param n a name
250: * @param q a query
251: * @param conMan the given connection manager.
252: */
253: public SQLPredicate(String n, String q, SQLConnectionManager conMan) {
254: this (n, q, conMan.getDataSource());
255: }
256:
257: /**
258: * Set the name.
259: * @param n a name
260: */
261: public void setName(String n) {
262: name = n;
263: }
264:
265: /**
266: * Convert the object to a string.
267: * @return a string
268: */
269: public String toString() {
270: return (name == null) ? super .toString() : name;
271: }
272:
273: /**
274: * Indicates whether the object (usually a term or a clause set) can be performed
275: * using the java semantics.
276: * @return false
277: */
278: public boolean isExecutable() {
279: return true;
280: }
281:
282: /**
283: * Compare objects.
284: * @param obj another object
285: * @return a boolean
286: */
287: public boolean equals(Object obj) {
288: if ((obj != null) && (obj instanceof SQLPredicate)) {
289: SQLPredicate p = (SQLPredicate) obj;
290: boolean result = true;
291:
292: // compare query, name and data source
293: // bugfix in 1.8 - thanks to chenjb@gsta.com
294: result = (name == null) ? (p.name == null) : name
295: .equals(p.name);
296: result = result
297: && ((dataSource == null) ? (p.dataSource == null)
298: : dataSource.equals(p.dataSource));
299: result = result
300: && ((query == null) ? (p.query == null) : query
301: .equals(p.query));
302: result = result
303: && ((typeMapping == null) ? (p.typeMapping == null)
304: : typeMapping.equals(p.typeMapping));
305:
306: Class[] c1 = p.structure;
307: Class[] c2 = structure;
308:
309: if (c1 == null) {
310: result = c2 == null;
311: } else {
312: result = result && (c1.length == c2.length);
313:
314: for (int i = 0; i < c1.length; i++) {
315: result = result && (c1[i] == c2[i]);
316: }
317: }
318:
319: return result;
320: } else {
321: return false;
322: }
323: }
324:
325: /**
326: * Get the hashcode of the object.
327: * @return the hash code of the object
328: */
329: public int hashCode() {
330: return ((name == null) ? 0 : name.hashCode())
331: ^ ((query == null) ? 0 : query.hashCode())
332: ^ ((dataSource == null) ? 0 : dataSource.hashCode());
333: }
334:
335: /**
336: * Get the slot names.
337: * @return an array of strings, the length of the array is the same as
338: * the length of the array of terms (the structure of the predicate)
339: */
340: public String[] getSlotNames() {
341: if (slotNames == null) {
342: slotNames = new String[getStructure().length];
343: for (int i = 0; i < slotNames.length; i++)
344: slotNames[i] = PredicateUtils.getSlotName(this , i);
345: }
346: return slotNames;
347: }
348:
349: /**
350: * Set the slot names.
351: * @param names an array of strings, the length of the array is the same as
352: * the length of the array of terms (the structure of the predicate)
353: */
354: public void setSlotNames(String[] names) {
355: if (names != null && names.length != getStructure().length)
356: throw new IllegalArgumentException(
357: "Number of slot names and number of slots must match - cannot set slot names for predicate "
358: + this );
359: this .slotNames = names;
360: }
361:
362: /**
363: * Indicates whether the slot names can be modified.
364: * @return a boolean
365: */
366: public boolean slotNamesCanBeEdited() {
367: return true;
368: }
369: }
|