001: package org.apache.ojb.broker.accesslayer.sql;
002:
003: /* Copyright 2002-2005 The Apache Software Foundation
004: *
005: * Licensed under the Apache License, Version 2.0 (the "License");
006: * you may not use this file except in compliance with the License.
007: * You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: import java.util.Collection;
019: import java.util.Enumeration;
020: import java.util.Map;
021:
022: import org.apache.commons.collections.map.ReferenceIdentityMap;
023: import org.apache.ojb.broker.metadata.ClassDescriptor;
024: import org.apache.ojb.broker.metadata.ProcedureDescriptor;
025: import org.apache.ojb.broker.platforms.Platform;
026: import org.apache.ojb.broker.query.BetweenCriteria;
027: import org.apache.ojb.broker.query.Criteria;
028: import org.apache.ojb.broker.query.ExistsCriteria;
029: import org.apache.ojb.broker.query.FieldCriteria;
030: import org.apache.ojb.broker.query.InCriteria;
031: import org.apache.ojb.broker.query.NullCriteria;
032: import org.apache.ojb.broker.query.Query;
033: import org.apache.ojb.broker.query.SelectionCriteria;
034: import org.apache.ojb.broker.query.SqlCriteria;
035: import org.apache.ojb.broker.util.logging.Logger;
036: import org.apache.ojb.broker.util.logging.LoggerFactory;
037:
038: /**
039: * This Class is responsible for building sql statements
040: * Objects fields and their repective values are accessed by Java reflection
041: *
042: * @author <a href="mailto:thma@apache.org">Thomas Mahler<a>
043: * @author <a href="mailto:rgallagh@bellsouth.net">Ron Gallagher</a>
044: * @author <a href="mailto:rburt3@mchsi.com">Randall Burt</a>
045: * @version $Id: SqlGeneratorDefaultImpl.java,v 1.23.2.5 2005/12/21 22:23:44 tomdz Exp $
046: */
047: public class SqlGeneratorDefaultImpl implements SqlGenerator {
048: private Logger logger = LoggerFactory
049: .getLogger(SqlGeneratorDefaultImpl.class);
050: private Platform m_platform;
051: /*
052: arminw:
053: TODO: In ClassDescriptor we need support for "field change event" listener if we allow
054: to change metadata at runtime.
055: Further on we have to deal with weak references to allow GC of outdated Metadata classes
056: (key=cld of map have to be a weak reference and the metadata used in the SqlStatement classes too,
057: because inner class SqlForClass indirectly refer the key=cld in some cases and SqlStatement
058: implementation classes have references to metadata classes too).
059:
060: Field changes are not reflected in this implementation!
061: */
062: /** Cache for {@link SqlForClass} instances, keyed per class descriptor. */
063: private Map sqlForClass = new ReferenceIdentityMap(
064: ReferenceIdentityMap.WEAK, ReferenceIdentityMap.HARD);
065:
066: public SqlGeneratorDefaultImpl(Platform platform) {
067: this .m_platform = platform;
068: }
069:
070: /**
071: * generate a prepared DELETE-Statement for the Class
072: * described by cld.
073: * @param cld the ClassDescriptor
074: */
075: public SqlStatement getPreparedDeleteStatement(ClassDescriptor cld) {
076: SqlForClass sfc = getSqlForClass(cld);
077: SqlStatement sql = sfc.getDeleteSql();
078: if (sql == null) {
079: ProcedureDescriptor pd = cld.getDeleteProcedure();
080:
081: if (pd == null) {
082: sql = new SqlDeleteByPkStatement(cld, logger);
083: } else {
084: sql = new SqlProcedureStatement(pd, logger);
085: }
086: // set the sql string
087: sfc.setDeleteSql(sql);
088:
089: if (logger.isDebugEnabled()) {
090: logger.debug("SQL:" + sql.getStatement());
091: }
092: }
093: return sql;
094: }
095:
096: /**
097: * generate a prepared INSERT-Statement for the Class
098: * described by cld.
099: *
100: * @param cld the ClassDescriptor
101: */
102: public SqlStatement getPreparedInsertStatement(ClassDescriptor cld) {
103: SqlStatement sql;
104: SqlForClass sfc = getSqlForClass(cld);
105: sql = sfc.getInsertSql();
106: if (sql == null) {
107: ProcedureDescriptor pd = cld.getInsertProcedure();
108:
109: if (pd == null) {
110: sql = new SqlInsertStatement(cld, logger);
111: } else {
112: sql = new SqlProcedureStatement(pd, logger);
113: }
114: // set the sql string
115: sfc.setInsertSql(sql);
116:
117: if (logger.isDebugEnabled()) {
118: logger.debug("SQL:" + sql.getStatement());
119: }
120: }
121: return sql;
122: }
123:
124: /**
125: * generate a prepared SELECT-Statement for the Class
126: * described by cld
127: * @param cld the ClassDescriptor
128: */
129: public SelectStatement getPreparedSelectByPkStatement(
130: ClassDescriptor cld) {
131: SelectStatement sql;
132: SqlForClass sfc = getSqlForClass(cld);
133: sql = sfc.getSelectByPKSql();
134: if (sql == null) {
135: sql = new SqlSelectByPkStatement(m_platform, cld, logger);
136:
137: // set the sql string
138: sfc.setSelectByPKSql(sql);
139:
140: if (logger.isDebugEnabled()) {
141: logger.debug("SQL:" + sql.getStatement());
142: }
143: }
144: return sql;
145: }
146:
147: public SqlStatement getPreparedExistsStatement(ClassDescriptor cld) {
148: SqlStatement sql;
149: SqlForClass sfc = getSqlForClass(cld);
150: sql = sfc.getSelectExists();
151: if (sql == null) {
152: // TODO: Should we support a procedure call for this too??
153: sql = new SqlExistStatement(cld, logger);
154: // set the sql string
155: sfc.setSelectExists(sql);
156: if (logger.isDebugEnabled()) {
157: logger.debug("SQL:" + sql.getStatement());
158: }
159: }
160: return sql;
161: }
162:
163: /**
164: * generate a select-Statement according to query
165: *
166: * @param query the Query
167: * @param cld the ClassDescriptor
168: */
169: public SelectStatement getPreparedSelectStatement(Query query,
170: ClassDescriptor cld) {
171: SelectStatement sql = new SqlSelectStatement(m_platform, cld,
172: query, logger);
173: if (logger.isDebugEnabled()) {
174: logger.debug("SQL:" + sql.getStatement());
175: }
176: return sql;
177: }
178:
179: /**
180: * generate a prepared UPDATE-Statement for the Class
181: * described by cld
182: * @param cld the ClassDescriptor
183: */
184: public SqlStatement getPreparedUpdateStatement(ClassDescriptor cld) {
185: SqlForClass sfc = getSqlForClass(cld);
186: SqlStatement result = sfc.getUpdateSql();
187: if (result == null) {
188: ProcedureDescriptor pd = cld.getUpdateProcedure();
189:
190: if (pd == null) {
191: result = new SqlUpdateStatement(cld, logger);
192: } else {
193: result = new SqlProcedureStatement(pd, logger);
194: }
195: // set the sql string
196: sfc.setUpdateSql(result);
197:
198: if (logger.isDebugEnabled()) {
199: logger.debug("SQL:" + result.getStatement());
200: }
201: }
202: return result;
203: }
204:
205: /**
206: * generate an INSERT-Statement for M:N indirection table
207: *
208: * @param table
209: * @param pkColumns1
210: * @param pkColumns2
211: */
212: public String getInsertMNStatement(String table,
213: String[] pkColumns1, String[] pkColumns2) {
214: SqlStatement sql;
215: String result;
216:
217: String[] cols = new String[pkColumns1.length
218: + pkColumns2.length];
219: System.arraycopy(pkColumns1, 0, cols, 0, pkColumns1.length);
220: System.arraycopy(pkColumns2, 0, cols, pkColumns1.length,
221: pkColumns2.length);
222:
223: sql = new SqlInsertMNStatement(table, cols, logger);
224: result = sql.getStatement();
225:
226: if (logger.isDebugEnabled()) {
227: logger.debug("SQL:" + result);
228: }
229: return result;
230: }
231:
232: /**
233: * generate a SELECT-Statement for M:N indirection table
234: *
235: * @param table the indirection table
236: * @param selectColumns selected columns
237: * @param columns for where
238: */
239: public String getSelectMNStatement(String table,
240: String[] selectColumns, String[] columns) {
241: SqlStatement sql;
242: String result;
243:
244: sql = new SqlSelectMNStatement(table, selectColumns, columns,
245: logger);
246: result = sql.getStatement();
247:
248: if (logger.isDebugEnabled()) {
249: logger.debug("SQL:" + result);
250: }
251: return result;
252: }
253:
254: /**
255: * generate a DELETE-Statement for M:N indirection table
256: *
257: * @param table
258: * @param pkColumns1
259: * @param pkColumns2
260: */
261: public String getDeleteMNStatement(String table,
262: String[] pkColumns1, String[] pkColumns2) {
263: SqlStatement sql;
264: String result;
265: String[] cols;
266:
267: if (pkColumns2 == null) {
268: cols = pkColumns1;
269: } else {
270: cols = new String[pkColumns1.length + pkColumns2.length];
271: System.arraycopy(pkColumns1, 0, cols, 0, pkColumns1.length);
272: System.arraycopy(pkColumns2, 0, cols, pkColumns1.length,
273: pkColumns2.length);
274: }
275:
276: sql = new SqlDeleteMNStatement(table, cols, logger);
277: result = sql.getStatement();
278:
279: if (logger.isDebugEnabled()) {
280: logger.debug("SQL:" + result);
281: }
282: return result;
283: }
284:
285: /**
286: * generate a select-Statement according to query
287: * @param query the Query
288: * @param cld the ClassDescriptor
289: */
290: public SelectStatement getSelectStatementDep(Query query,
291: ClassDescriptor cld) {
292: // TODO: Why do we need this method?
293: return getPreparedSelectStatement(query, cld);
294: }
295:
296: /**
297: * @param crit Selection criteria
298: *
299: * 26/06/99 Change statement to a StringBuffer for efficiency
300: */
301: public String asSQLStatement(Criteria crit, ClassDescriptor cld) {
302: Enumeration e = crit.getElements();
303: StringBuffer statement = new StringBuffer();
304: while (e.hasMoreElements()) {
305: Object o = e.nextElement();
306: if (o instanceof Criteria) {
307: String addAtStart;
308: String addAtEnd;
309: Criteria pc = (Criteria) o;
310: // need to add parenthesises?
311: if (pc.isEmbraced()) {
312: addAtStart = " (";
313: addAtEnd = ") ";
314: } else {
315: addAtStart = "";
316: addAtEnd = "";
317: }
318:
319: switch (pc.getType()) {
320: case (Criteria.OR): {
321: statement.append(" OR ").append(addAtStart);
322: statement.append(asSQLStatement(pc, cld));
323: statement.append(addAtEnd);
324: break;
325: }
326: case (Criteria.AND): {
327: statement.insert(0, "( ");
328: statement.append(") ");
329: statement.append(" AND ").append(addAtStart);
330: statement.append(asSQLStatement(pc, cld));
331: statement.append(addAtEnd);
332: break;
333: }
334: }
335: } else {
336: SelectionCriteria c = (SelectionCriteria) o;
337: if (statement.length() == 0) {
338: statement.append(asSQLClause(c, cld));
339: } else {
340: statement.insert(0, "(");
341: statement.append(") ");
342: statement.append(" AND ");
343: statement.append(asSQLClause(c, cld));
344: }
345: }
346: } // while
347: if (statement.length() == 0) {
348: return null;
349: }
350: return statement.toString();
351: }
352:
353: /**
354: * Answer the SQL-Clause for a SelectionCriteria
355: *
356: * @param c SelectionCriteria
357: * @param cld ClassDescriptor
358: */
359: protected String asSQLClause(SelectionCriteria c,
360: ClassDescriptor cld) {
361: if (c instanceof FieldCriteria)
362: return toSQLClause((FieldCriteria) c, cld);
363:
364: if (c instanceof NullCriteria)
365: return toSQLClause((NullCriteria) c);
366:
367: if (c instanceof BetweenCriteria)
368: return toSQLClause((BetweenCriteria) c, cld);
369:
370: if (c instanceof InCriteria)
371: return toSQLClause((InCriteria) c);
372:
373: if (c instanceof SqlCriteria)
374: return toSQLClause((SqlCriteria) c);
375:
376: if (c instanceof ExistsCriteria)
377: return toSQLClause((ExistsCriteria) c, cld);
378:
379: return toSQLClause(c, cld);
380: }
381:
382: private String toSqlClause(Object attributeOrQuery,
383: ClassDescriptor cld) {
384: String result;
385:
386: if (attributeOrQuery instanceof Query) {
387: Query q = (Query) attributeOrQuery;
388: result = getPreparedSelectStatement(
389: q,
390: cld.getRepository().getDescriptorFor(
391: q.getSearchClass())).getStatement();
392: } else {
393: result = (String) attributeOrQuery;
394: }
395:
396: return result;
397: }
398:
399: /**
400: * Answer the SQL-Clause for a NullCriteria
401: *
402: * @param c NullCriteria
403: */
404: private String toSQLClause(NullCriteria c) {
405: String colName = (String) c.getAttribute();
406: return colName + c.getClause();
407: }
408:
409: /**
410: * Answer the SQL-Clause for a FieldCriteria
411: *
412: * @param c FieldCriteria
413: * @param cld ClassDescriptor
414: */
415: private String toSQLClause(FieldCriteria c, ClassDescriptor cld) {
416: String colName = toSqlClause(c.getAttribute(), cld);
417: return colName + c.getClause() + c.getValue();
418: }
419:
420: /**
421: * Answer the SQL-Clause for a BetweenCriteria
422: *
423: * @param c BetweenCriteria
424: * @param cld ClassDescriptor
425: */
426: private String toSQLClause(BetweenCriteria c, ClassDescriptor cld) {
427: String colName = toSqlClause(c.getAttribute(), cld);
428: return colName + c.getClause() + " ? AND ? ";
429: }
430:
431: /**
432: * Answer the SQL-Clause for an InCriteria
433: *
434: * @param c SelectionCriteria
435: */
436: private String toSQLClause(InCriteria c) {
437: StringBuffer buf = new StringBuffer();
438: Collection values = (Collection) c.getValue();
439: int size = values.size();
440:
441: buf.append(c.getAttribute());
442: buf.append(c.getClause());
443: buf.append("(");
444: for (int i = 0; i < size - 1; i++) {
445: buf.append("?,");
446: }
447: buf.append("?)");
448: return buf.toString();
449: }
450:
451: /**
452: * Answer the SQL-Clause for a SelectionCriteria
453: *
454: * @param c SelectionCriteria
455: * @param cld ClassDescriptor
456: */
457: private String toSQLClause(SelectionCriteria c, ClassDescriptor cld) {
458: String colName = toSqlClause(c.getAttribute(), cld);
459: return colName + c.getClause() + " ? ";
460: }
461:
462: /**
463: * Answer the SQL-Clause for a SqlCriteria
464: *
465: * @param c SqlCriteria
466: */
467: private String toSQLClause(SqlCriteria c) {
468: return c.getClause();
469: }
470:
471: /**
472: * Answer the SQL-Clause for an ExistsCriteria
473: *
474: * @param c ExistsCriteria
475: * @param cld ClassDescriptor
476: */
477: private String toSQLClause(ExistsCriteria c, ClassDescriptor cld) {
478: StringBuffer buf = new StringBuffer();
479: Query subQuery = (Query) c.getValue();
480:
481: buf.append(c.getClause());
482: buf.append(" (");
483:
484: // If it's a proper call
485: if (cld != null) {
486: buf.append(getPreparedSelectStatement(subQuery, cld
487: .getRepository().getDescriptorFor(
488: subQuery.getSearchClass())));
489:
490: // Otherwise it's most likely a call to toString()
491: } else {
492: buf.append(subQuery);
493: }
494:
495: buf.append(")");
496: return buf.toString();
497: }
498:
499: /**
500: * generate a prepared DELETE-Statement according to query
501: * @param query the Query
502: * @param cld the ClassDescriptor
503: */
504: public SqlStatement getPreparedDeleteStatement(Query query,
505: ClassDescriptor cld) {
506: return new SqlDeleteByQuery(m_platform, cld, query, logger);
507: }
508:
509: /* (non-Javadoc)
510: * @see org.apache.ojb.broker.accesslayer.sql.SqlGenerator#getPlatform()
511: */
512: public Platform getPlatform() {
513: return m_platform;
514: }
515:
516: /**
517: * Returns the {@link SqlForClass} instance for
518: * the given class descriptor.
519: *
520: * @param cld The class descriptor.
521: * @return The {@link SqlForClass}.
522: */
523: protected SqlForClass getSqlForClass(ClassDescriptor cld) {
524: SqlForClass result = (SqlForClass) sqlForClass.get(cld);
525: if (result == null) {
526: result = newInstanceSqlForClass();
527: sqlForClass.put(cld, result);
528: }
529: return result;
530: }
531:
532: /**
533: * User who want to extend this implementation can override this method to use
534: * their own (extended) version of
535: * {@link org.apache.ojb.broker.accesslayer.sql.SqlGeneratorDefaultImpl.SqlForClass}.
536: *
537: * @return A new instance.
538: */
539: protected SqlForClass newInstanceSqlForClass() {
540: return new SqlForClass();
541: }
542:
543: //===================================================================
544: // inner class
545: //===================================================================
546:
547: /**
548: * This class serves as a cache for sql-Statements
549: * used for persistence operations.
550: */
551: public static class SqlForClass {
552: private SqlStatement deleteSql;
553: private SqlStatement insertSql;
554: private SqlStatement updateSql;
555: private SelectStatement selectByPKSql;
556: private SqlStatement selectExists;
557:
558: public SqlStatement getDeleteSql() {
559: return deleteSql;
560: }
561:
562: public void setDeleteSql(SqlStatement deleteSql) {
563: this .deleteSql = deleteSql;
564: }
565:
566: public SqlStatement getInsertSql() {
567: return insertSql;
568: }
569:
570: public void setInsertSql(SqlStatement insertSql) {
571: this .insertSql = insertSql;
572: }
573:
574: public SqlStatement getUpdateSql() {
575: return updateSql;
576: }
577:
578: public void setUpdateSql(SqlStatement updateSql) {
579: this .updateSql = updateSql;
580: }
581:
582: public SelectStatement getSelectByPKSql() {
583: return selectByPKSql;
584: }
585:
586: public void setSelectByPKSql(SelectStatement selectByPKSql) {
587: this .selectByPKSql = selectByPKSql;
588: }
589:
590: public SqlStatement getSelectExists() {
591: return selectExists;
592: }
593:
594: public void setSelectExists(SqlStatement selectExists) {
595: this.selectExists = selectExists;
596: }
597: }
598:
599: }
|