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.1 2007-08-24 22:17:39 ewestfal Exp $
046 */
047 public class SqlGeneratorDefaultImpl implements SqlGenerator
048 {
049 private Logger logger = LoggerFactory.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(ReferenceIdentityMap.WEAK, ReferenceIdentityMap.HARD);
064
065 public SqlGeneratorDefaultImpl(Platform platform)
066 {
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 {
077 SqlForClass sfc = getSqlForClass(cld);
078 SqlStatement sql = sfc.getDeleteSql();
079 if(sql == null)
080 {
081 ProcedureDescriptor pd = cld.getDeleteProcedure();
082
083 if(pd == null)
084 {
085 sql = new SqlDeleteByPkStatement(cld, logger);
086 }
087 else
088 {
089 sql = new SqlProcedureStatement(pd, logger);
090 }
091 // set the sql string
092 sfc.setDeleteSql(sql);
093
094 if(logger.isDebugEnabled())
095 {
096 logger.debug("SQL:" + sql.getStatement());
097 }
098 }
099 return sql;
100 }
101
102 /**
103 * generate a prepared INSERT-Statement for the Class
104 * described by cld.
105 *
106 * @param cld the ClassDescriptor
107 */
108 public SqlStatement getPreparedInsertStatement(ClassDescriptor cld)
109 {
110 SqlStatement sql;
111 SqlForClass sfc = getSqlForClass(cld);
112 sql = sfc.getInsertSql();
113 if(sql == null)
114 {
115 ProcedureDescriptor pd = cld.getInsertProcedure();
116
117 if(pd == null)
118 {
119 sql = new SqlInsertStatement(cld, logger);
120 }
121 else
122 {
123 sql = new SqlProcedureStatement(pd, logger);
124 }
125 // set the sql string
126 sfc.setInsertSql(sql);
127
128 if(logger.isDebugEnabled())
129 {
130 logger.debug("SQL:" + sql.getStatement());
131 }
132 }
133 return sql;
134 }
135
136 /**
137 * generate a prepared SELECT-Statement for the Class
138 * described by cld
139 * @param cld the ClassDescriptor
140 */
141 public SelectStatement getPreparedSelectByPkStatement(ClassDescriptor cld)
142 {
143 SelectStatement sql;
144 SqlForClass sfc = getSqlForClass(cld);
145 sql = sfc.getSelectByPKSql();
146 if(sql == null)
147 {
148 sql = new SqlSelectByPkStatement(m_platform, cld, logger);
149
150 // set the sql string
151 sfc.setSelectByPKSql(sql);
152
153 if(logger.isDebugEnabled())
154 {
155 logger.debug("SQL:" + sql.getStatement());
156 }
157 }
158 return sql;
159 }
160
161 public SqlStatement getPreparedExistsStatement(ClassDescriptor cld)
162 {
163 SqlStatement sql;
164 SqlForClass sfc = getSqlForClass(cld);
165 sql = sfc.getSelectExists();
166 if(sql == null)
167 {
168 // TODO: Should we support a procedure call for this too??
169 sql = new SqlExistStatement(cld, logger);
170 // set the sql string
171 sfc.setSelectExists(sql);
172 if(logger.isDebugEnabled())
173 {
174 logger.debug("SQL:" + sql.getStatement());
175 }
176 }
177 return sql;
178 }
179
180 /**
181 * generate a select-Statement according to query
182 *
183 * @param query the Query
184 * @param cld the ClassDescriptor
185 */
186 public SelectStatement getPreparedSelectStatement(Query query, ClassDescriptor cld)
187 {
188 SelectStatement sql = new SqlSelectStatement(m_platform, cld, query, logger);
189 if (logger.isDebugEnabled())
190 {
191 logger.debug("SQL:" + sql.getStatement());
192 }
193 return sql;
194 }
195
196 /**
197 * generate a prepared UPDATE-Statement for the Class
198 * described by cld
199 * @param cld the ClassDescriptor
200 */
201 public SqlStatement getPreparedUpdateStatement(ClassDescriptor cld)
202 {
203 SqlForClass sfc = getSqlForClass(cld);
204 SqlStatement result = sfc.getUpdateSql();
205 if(result == null)
206 {
207 ProcedureDescriptor pd = cld.getUpdateProcedure();
208
209 if(pd == null)
210 {
211 result = new SqlUpdateStatement(cld, logger);
212 }
213 else
214 {
215 result = new SqlProcedureStatement(pd, logger);
216 }
217 // set the sql string
218 sfc.setUpdateSql(result);
219
220 if(logger.isDebugEnabled())
221 {
222 logger.debug("SQL:" + result.getStatement());
223 }
224 }
225 return result;
226 }
227
228 /**
229 * generate an INSERT-Statement for M:N indirection table
230 *
231 * @param table
232 * @param pkColumns1
233 * @param pkColumns2
234 */
235 public String getInsertMNStatement(String table, String[] pkColumns1, String[] pkColumns2)
236 {
237 SqlStatement sql;
238 String result;
239
240 String[] cols = new String[pkColumns1.length + pkColumns2.length];
241 System.arraycopy(pkColumns1, 0, cols, 0, pkColumns1.length);
242 System.arraycopy(pkColumns2, 0, cols, pkColumns1.length, pkColumns2.length);
243
244 sql = new SqlInsertMNStatement(table, cols, logger);
245 result = sql.getStatement();
246
247 if (logger.isDebugEnabled())
248 {
249 logger.debug("SQL:" + result);
250 }
251 return result;
252 }
253
254 /**
255 * generate a SELECT-Statement for M:N indirection table
256 *
257 * @param table the indirection table
258 * @param selectColumns selected columns
259 * @param columns for where
260 */
261 public String getSelectMNStatement(String table, String[] selectColumns, String[] columns)
262 {
263 SqlStatement sql;
264 String result;
265
266 sql = new SqlSelectMNStatement(table, selectColumns, columns, logger);
267 result = sql.getStatement();
268
269 if (logger.isDebugEnabled())
270 {
271 logger.debug("SQL:" + result);
272 }
273 return result;
274 }
275
276 /**
277 * generate a DELETE-Statement for M:N indirection table
278 *
279 * @param table
280 * @param pkColumns1
281 * @param pkColumns2
282 */
283 public String getDeleteMNStatement(String table, String[] pkColumns1, String[] pkColumns2)
284 {
285 SqlStatement sql;
286 String result;
287 String[] cols;
288
289 if (pkColumns2 == null)
290 {
291 cols = pkColumns1;
292 }
293 else
294 {
295 cols = new String[pkColumns1.length + pkColumns2.length];
296 System.arraycopy(pkColumns1, 0, cols, 0, pkColumns1.length);
297 System.arraycopy(pkColumns2, 0, cols, pkColumns1.length, pkColumns2.length);
298 }
299
300 sql = new SqlDeleteMNStatement(table, cols, logger);
301 result = sql.getStatement();
302
303 if (logger.isDebugEnabled())
304 {
305 logger.debug("SQL:" + result);
306 }
307 return result;
308 }
309
310 /**
311 * generate a select-Statement according to query
312 * @param query the Query
313 * @param cld the ClassDescriptor
314 */
315 public SelectStatement getSelectStatementDep(Query query, ClassDescriptor cld)
316 {
317 // TODO: Why do we need this method?
318 return getPreparedSelectStatement(query, cld);
319 }
320
321 /**
322 * @param crit Selection criteria
323 *
324 * 26/06/99 Change statement to a StringBuffer for efficiency
325 */
326 public String asSQLStatement(Criteria crit, ClassDescriptor cld)
327 {
328 Enumeration e = crit.getElements();
329 StringBuffer statement = new StringBuffer();
330 while (e.hasMoreElements())
331 {
332 Object o = e.nextElement();
333 if (o instanceof Criteria)
334 {
335 String addAtStart;
336 String addAtEnd;
337 Criteria pc = (Criteria) o;
338 // need to add parenthesises?
339 if (pc.isEmbraced())
340 {
341 addAtStart = " (";
342 addAtEnd = ") ";
343 }
344 else
345 {
346 addAtStart = "";
347 addAtEnd = "";
348 }
349
350 switch (pc.getType())
351 {
352 case (Criteria.OR) :
353 {
354 statement.append(" OR ").append(addAtStart);
355 statement.append(asSQLStatement(pc, cld));
356 statement.append(addAtEnd);
357 break;
358 }
359 case (Criteria.AND) :
360 {
361 statement.insert(0, "( ");
362 statement.append(") ");
363 statement.append(" AND ").append(addAtStart);
364 statement.append(asSQLStatement(pc, cld));
365 statement.append(addAtEnd);
366 break;
367 }
368 }
369 }
370 else
371 {
372 SelectionCriteria c = (SelectionCriteria) o;
373 if (statement.length() == 0)
374 {
375 statement.append(asSQLClause(c, cld));
376 }
377 else
378 {
379 statement.insert(0, "(");
380 statement.append(") ");
381 statement.append(" AND ");
382 statement.append(asSQLClause(c, cld));
383 }
384 }
385 } // while
386 if (statement.length() == 0)
387 {
388 return null;
389 }
390 return statement.toString();
391 }
392
393 /**
394 * Answer the SQL-Clause for a SelectionCriteria
395 *
396 * @param c SelectionCriteria
397 * @param cld ClassDescriptor
398 */
399 protected String asSQLClause(SelectionCriteria c, ClassDescriptor cld)
400 {
401 if (c instanceof FieldCriteria)
402 return toSQLClause((FieldCriteria) c, cld);
403
404 if (c instanceof NullCriteria)
405 return toSQLClause((NullCriteria) c);
406
407 if (c instanceof BetweenCriteria)
408 return toSQLClause((BetweenCriteria) c, cld);
409
410 if (c instanceof InCriteria)
411 return toSQLClause((InCriteria) c);
412
413 if (c instanceof SqlCriteria)
414 return toSQLClause((SqlCriteria) c);
415
416 if (c instanceof ExistsCriteria)
417 return toSQLClause((ExistsCriteria) c, cld);
418
419 return toSQLClause(c, cld);
420 }
421
422 private String toSqlClause(Object attributeOrQuery, ClassDescriptor cld)
423 {
424 String result;
425
426 if (attributeOrQuery instanceof Query)
427 {
428 Query q = (Query) attributeOrQuery;
429 result = getPreparedSelectStatement(q, cld.getRepository().getDescriptorFor(q.getSearchClass()))
430 .getStatement();
431 }
432 else
433 {
434 result = (String)attributeOrQuery;
435 }
436
437 return result;
438 }
439
440 /**
441 * Answer the SQL-Clause for a NullCriteria
442 *
443 * @param c NullCriteria
444 */
445 private String toSQLClause(NullCriteria c)
446 {
447 String colName = (String)c.getAttribute();
448 return colName + c.getClause();
449 }
450
451 /**
452 * Answer the SQL-Clause for a FieldCriteria
453 *
454 * @param c FieldCriteria
455 * @param cld ClassDescriptor
456 */
457 private String toSQLClause(FieldCriteria c, ClassDescriptor cld)
458 {
459 String colName = toSqlClause(c.getAttribute(), cld);
460 return colName + c.getClause() + c.getValue();
461 }
462
463 /**
464 * Answer the SQL-Clause for a BetweenCriteria
465 *
466 * @param c BetweenCriteria
467 * @param cld ClassDescriptor
468 */
469 private String toSQLClause(BetweenCriteria c, ClassDescriptor cld)
470 {
471 String colName = toSqlClause(c.getAttribute(), cld);
472 return colName + c.getClause() + " ? AND ? ";
473 }
474
475 /**
476 * Answer the SQL-Clause for an InCriteria
477 *
478 * @param c SelectionCriteria
479 */
480 private String toSQLClause(InCriteria c)
481 {
482 StringBuffer buf = new StringBuffer();
483 Collection values = (Collection) c.getValue();
484 int size = values.size();
485
486 buf.append(c.getAttribute());
487 buf.append(c.getClause());
488 buf.append("(");
489 for (int i = 0; i < size - 1; i++)
490 {
491 buf.append("?,");
492 }
493 buf.append("?)");
494 return buf.toString();
495 }
496
497 /**
498 * Answer the SQL-Clause for a SelectionCriteria
499 *
500 * @param c SelectionCriteria
501 * @param cld ClassDescriptor
502 */
503 private String toSQLClause(SelectionCriteria c, ClassDescriptor cld)
504 {
505 String colName = toSqlClause(c.getAttribute(), cld);
506 return colName + c.getClause() + " ? ";
507 }
508
509 /**
510 * Answer the SQL-Clause for a SqlCriteria
511 *
512 * @param c SqlCriteria
513 */
514 private String toSQLClause(SqlCriteria c)
515 {
516 return c.getClause();
517 }
518
519 /**
520 * Answer the SQL-Clause for an ExistsCriteria
521 *
522 * @param c ExistsCriteria
523 * @param cld ClassDescriptor
524 */
525 private String toSQLClause(ExistsCriteria c, ClassDescriptor cld)
526 {
527 StringBuffer buf = new StringBuffer();
528 Query subQuery = (Query) c.getValue();
529
530 buf.append(c.getClause());
531 buf.append(" (");
532
533 // If it's a proper call
534 if (cld != null)
535 {
536 buf.append(
537 getPreparedSelectStatement(
538 subQuery,
539 cld.getRepository().getDescriptorFor(subQuery.getSearchClass())));
540
541 // Otherwise it's most likely a call to toString()
542 }
543 else
544 {
545 buf.append(subQuery);
546 }
547
548 buf.append(")");
549 return buf.toString();
550 }
551
552 /**
553 * generate a prepared DELETE-Statement according to query
554 * @param query the Query
555 * @param cld the ClassDescriptor
556 */
557 public SqlStatement getPreparedDeleteStatement(Query query, ClassDescriptor cld)
558 {
559 return new SqlDeleteByQuery(m_platform, cld, query, logger);
560 }
561
562 /* (non-Javadoc)
563 * @see org.apache.ojb.broker.accesslayer.sql.SqlGenerator#getPlatform()
564 */
565 public Platform getPlatform()
566 {
567 return m_platform;
568 }
569
570 /**
571 * Returns the {@link SqlForClass} instance for
572 * the given class descriptor.
573 *
574 * @param cld The class descriptor.
575 * @return The {@link SqlForClass}.
576 */
577 protected SqlForClass getSqlForClass(ClassDescriptor cld)
578 {
579 SqlForClass result = (SqlForClass) sqlForClass.get(cld);
580 if(result == null)
581 {
582 result = newInstanceSqlForClass();
583 sqlForClass.put(cld, result);
584 }
585 return result;
586 }
587
588 /**
589 * User who want to extend this implementation can override this method to use
590 * their own (extended) version of
591 * {@link org.apache.ojb.broker.accesslayer.sql.SqlGeneratorDefaultImpl.SqlForClass}.
592 *
593 * @return A new instance.
594 */
595 protected SqlForClass newInstanceSqlForClass()
596 {
597 return new SqlForClass();
598 }
599
600 //===================================================================
601 // inner class
602 //===================================================================
603
604 /**
605 * This class serves as a cache for sql-Statements
606 * used for persistence operations.
607 */
608 public static class SqlForClass
609 {
610 private SqlStatement deleteSql;
611 private SqlStatement insertSql;
612 private SqlStatement updateSql;
613 private SelectStatement selectByPKSql;
614 private SqlStatement selectExists;
615
616 public SqlStatement getDeleteSql()
617 {
618 return deleteSql;
619 }
620
621 public void setDeleteSql(SqlStatement deleteSql)
622 {
623 this.deleteSql = deleteSql;
624 }
625
626 public SqlStatement getInsertSql()
627 {
628 return insertSql;
629 }
630
631 public void setInsertSql(SqlStatement insertSql)
632 {
633 this.insertSql = insertSql;
634 }
635
636 public SqlStatement getUpdateSql()
637 {
638 return updateSql;
639 }
640
641 public void setUpdateSql(SqlStatement updateSql)
642 {
643 this.updateSql = updateSql;
644 }
645
646 public SelectStatement getSelectByPKSql()
647 {
648 return selectByPKSql;
649 }
650
651 public void setSelectByPKSql(SelectStatement selectByPKSql)
652 {
653 this.selectByPKSql = selectByPKSql;
654 }
655
656 public SqlStatement getSelectExists()
657 {
658 return selectExists;
659 }
660
661 public void setSelectExists(SqlStatement selectExists)
662 {
663 this.selectExists = selectExists;
664 }
665 }
666
667 }