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    }