001package gu.sql2java;
002
003import java.lang.reflect.Array;
004import java.nio.ByteBuffer;
005import java.sql.Connection;
006import java.sql.PreparedStatement;
007import java.sql.ResultSet;
008import java.sql.SQLException;
009import java.sql.Statement;
010import java.util.ArrayList;
011import java.util.Arrays;
012import java.util.Collection;
013import java.util.Collections;
014import java.util.Iterator;
015import java.util.LinkedHashMap;
016import java.util.LinkedHashSet;
017import java.util.LinkedList;
018import java.util.List;
019import java.util.Map;
020import java.util.ServiceLoader;
021import java.util.Map.Entry;
022import java.util.NoSuchElementException;
023import java.util.concurrent.Callable;
024import java.util.concurrent.atomic.AtomicInteger;
025
026import com.google.common.base.Function;
027import com.google.common.base.Joiner;
028import com.google.common.base.MoreObjects;
029import com.google.common.base.Predicate;
030import com.google.common.base.Throwables;
031import com.google.common.collect.ImmutableBiMap;
032import com.google.common.collect.ImmutableList;
033import com.google.common.collect.ImmutableMap;
034import com.google.common.collect.Iterables;
035import com.google.common.collect.Lists;
036import com.google.common.collect.Maps;
037import gu.sql2java.ForeignKeyMetaData.ForeignKeyRule;
038import gu.sql2java.Manager.AutoKeyRetrieveType;
039import gu.sql2java.exception.DaoException;
040import gu.sql2java.exception.DataAccessException;
041import gu.sql2java.exception.DataRetrievalException;
042import gu.sql2java.exception.ObjectRetrievalException;
043import gu.sql2java.exception.OptimisticLockingException;
044import gu.sql2java.exception.RuntimeDaoException;
045
046import static com.google.common.base.Preconditions.*;
047import static gu.sql2java.SimpleLog.*;
048
049/**
050 * implementation of {@link TableManager} 
051 * @author guyadong
052 *
053 * @param <B>  java bean type
054 */
055public class BaseTableManager<B extends BaseBean> implements TableManager<B>,Constant{
056    protected final RowMetaData metaData;
057    /** lazy load */
058        private volatile Map<String,TableListener<BaseBean>> foreignKeyDeleteListeners;
059        /** lazy load */
060        private volatile ListenerContainer<B> listenerContainer;
061        /** lazy load */
062        private volatile String generatedkeyStatement;
063        private static boolean debug = false;
064    protected BaseTableManager(String tablename){
065        metaData = RowMetaData.getMedaData(tablename);
066    }
067
068    public ListenerContainer<B> getListenerContainer() {
069                // double checking
070                if(listenerContainer == null){
071                        synchronized (this) {
072                                if(listenerContainer == null){
073                                        listenerContainer = new ListenerContainer<B>();
074                                }
075                        }
076                }
077                return listenerContainer;
078        }
079
080    /**
081     * @return map with foreignKey name TO TableListener
082     */
083    public Map<String, TableListener<BaseBean>> getForeignKeyDeleteListeners(){
084        // double checking
085        if(foreignKeyDeleteListeners == null){
086                synchronized (this) {
087                        if(foreignKeyDeleteListeners == null){
088                                LinkedHashMap<String, TableListener<BaseBean>> map = Maps.newLinkedHashMap();
089                                for(ForeignKeyMetaData fk : metaData.getForeignKeysForListener()){
090                                        map.put(fk.name, new DeleteRuleListener<BaseBean>(fk.name));
091                                }
092                                foreignKeyDeleteListeners = Collections.unmodifiableMap(map);
093                        }
094                        }
095        }
096        return foreignKeyDeleteListeners;
097    }
098        private String getGeneratedkeyStatement(Connection c){
099                // double check
100                if(generatedkeyStatement == null){
101                        synchronized (this) {
102                                if(generatedkeyStatement == null){
103                                        generatedkeyStatement = getManager().getGeneratedkeyStatement(c)
104                                                .replaceAll("<TABLE>", metaData.tablename )
105                                                .replaceAll("<KEY>", metaData.columnNameOf(metaData.autoincrementColumnId));
106                                }
107                        }
108                }
109                return generatedkeyStatement;           
110        }
111
112        /**
113     * Creates a new B instance.
114     *
115     * @return the new B instance
116     */
117        @SuppressWarnings("unchecked")
118    protected final B createBean()
119    {
120        try {
121                        return (B) metaData.beanType.newInstance();
122                } catch (Exception e) {
123                        Throwables.throwIfUnchecked(e);
124                        throw new RuntimeException(e);
125                }
126    }
127        /**
128         * Creates a new B instance.
129         * @param primaryValues values of primary keys
130         * @return B instance
131         */
132        protected final B createBean(Object... primaryValues)
133    {
134                checkArgument(primaryValues != null && primaryValues.length== metaData.primaryKeyNames.length,"INVALID primaryValues");
135                B bean = createBean();
136                int[] pkIds = metaData.primaryKeyIds;
137                for(int i=0;i<primaryValues.length;++i){
138                        int columnId = pkIds[i];
139                        Object value = primaryValues[i];
140                        checkArgument(null == value || metaData.columnTypeOf(columnId).isInstance(value),
141                                        "INVALID primkey type for %s,%s required",metaData.columnNameOf(columnId),metaData.columnTypeOf(columnId).getName());
142                        bean.setValue(columnId, value);
143                }
144                return bean;
145    }
146    private static int indexOfFirstNull(Object...objects) {
147                if(objects != null){
148                        for(int i=0;i< objects.length;++i){
149                                if(null == objects[i]){
150                                        return i;
151                                }
152                        }
153                        return -1;
154                }
155                return -2;
156        }
157        /**
158         * @param objects 
159         * @return true if any one of object is null or objects is null 
160         */
161        private static boolean hasNull(Object...objects) {
162                return indexOfFirstNull(objects) != -1;
163        }
164        
165        /**
166         * @param bean
167         * @return true if any primary key of B is null or B is null
168         */
169        private static <T extends BaseBean >boolean hasNullPk(T bean){
170                return (null == bean || hasNull(bean.primaryValues()));
171        }
172        private void prepareAutoincrement( Connection c,PreparedStatement ps, B bean) throws SQLException
173    {
174        if (!bean.isModified(metaData.autoincrementColumnId))
175        {
176            PreparedStatement ps2 = null;
177            ResultSet rs = null;
178            try {
179                if(AutoKeyRetrieveType.auto.equals(getManager().getGeneratedkeyRetrieveType(c))){
180                        rs = ps.getGeneratedKeys();
181                }else{
182                    ps2 = c.prepareStatement(getGeneratedkeyStatement(c));
183                        rs = ps2.executeQuery();
184                }
185                if(rs.next()) {
186                    setColumnValue(bean, 
187                                metaData.autoincrementColumnId, 
188                                Manager.getObject(rs,1,metaData.columnTypes.get(metaData.autoincrementColumnId)));
189                } else {
190                    throw new IllegalStateException("ATTENTION: Could not retrieve generated key!");
191                }
192            } finally {
193               getManager().close(ps2, rs);
194            }
195        }
196    }
197        
198    //13
199    /**
200     * Insert the B bean into the database.
201     * 
202     * @param bean the B bean to be saved
203     * @return the inserted bean
204     * @throws RuntimeDaoException
205     */
206    protected B insert(final B bean)
207    {
208        // mini checks
209        if (null == bean || !bean.isModified()) {
210            return bean; 
211        }
212        if (!bean.isNew()){
213            return this.update(bean);
214        }
215
216        Connection c = null;
217        PreparedStatement ps = null;
218
219                try
220        {
221            c = this.getConnection();
222            final AutoKeyRetrieveType retrieveType = getManager().getGeneratedkeyRetrieveType(c);
223            //-------------writePreInsert
224            if(metaData.autoincrementColumnId >= 0 
225                        && AutoKeyRetrieveType.before.equals(retrieveType)){
226                        prepareAutoincrement(c, null, bean);
227                }
228            //------------/writePreInsert
229            // listener callback
230            getListenerContainer().beforeInsert(bean);
231            StringBuilder sql = new StringBuilder("INSERT into " + metaData.tablename + " (");
232            List<String> modifiedList = Lists.newArrayList(Iterables.filter(metaData.columnNames,new Predicate<String>() {
233
234                        @Override
235                        public boolean apply(String input) {
236                                return bean.isModified(input);
237                        }
238                }));
239            String fields=Joiner.on(",").join(modifiedList);
240            // fields
241            sql.append(fields);
242            sql.append(") values (");
243            // values
244            sql.append(fields.replaceAll("[^,]+", "?"));
245            sql.append(")");
246            if(debug){
247                log("insert : " + sql.toString());
248            }
249            if(metaData.autoincrementColumnId >= 0 && AutoKeyRetrieveType.auto.equals(retrieveType)){
250                ps = c.prepareStatement(sql.toString(), Statement.RETURN_GENERATED_KEYS);
251            }else{
252                ps = c.prepareStatement(sql.toString(), ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
253            }
254            
255            fillPreparedStatement(ps, bean, SEARCH_EXACT, true);
256
257            ps.executeUpdate();
258            //------------------writePostInsert
259            if(metaData.autoincrementColumnId >= 0 && !AutoKeyRetrieveType.before.equals(retrieveType)){
260                        prepareAutoincrement(c, ps, bean);
261                }
262            //-------------------/writePostInsert
263            bean.setNew(false);
264            bean.resetIsModified();
265            // listener callback
266            getListenerContainer().afterInsert(bean);
267            return bean;
268        }
269        catch(SQLException e)
270        {
271            throw new RuntimeDaoException(new DataAccessException(e));
272        }
273        finally
274        {
275            // listener callback
276            getListenerContainer().done();
277            getManager().close(ps);
278            freeConnection(c);
279        }
280    }
281    
282    private static final Function<String, String> SQL_FUN1 = new Function<String,String>(){
283
284                @Override
285                public String apply(String input) {
286                        return input + "=?";
287                }};
288    //14
289    /**
290     * Update the B bean record in the database according to the changes.
291     *
292     * @param bean the B bean to be updated
293     * @return the updated bean
294     * @throws RuntimeDaoException
295     */
296    protected B update(final B bean) throws RuntimeDaoException
297    {
298        // mini checks
299        if (null == bean || !bean.isModified()) {
300            return bean;
301        }
302        if (bean.isNew()){
303            return this.insert(bean);
304        }
305
306        Connection c = null;
307        PreparedStatement ps = null;
308
309        try
310        {
311            c = this.getConnection();
312
313            // listener callback
314            getListenerContainer().beforeUpdate(bean); 
315            Object oldLockValue = null;
316            if(metaData.lockColumnType != null){
317                oldLockValue = bean.getValue(metaData.lockColumnName);
318                        // lockColumnType is String or Long
319                        if(String.class == metaData.lockColumnType){
320                                bean.setValue(metaData.lockColumnName,String.valueOf(System.currentTimeMillis())); 
321                        }else if(Long.class ==metaData.lockColumnType){
322                                bean.setValue(metaData.lockColumnName,System.currentTimeMillis()); 
323                        }else{
324                                throw new RuntimeException("INVALID LOCK COLUMN TYPE:String or Long required");
325                        }
326            }
327            StringBuilder sql = new StringBuilder("UPDATE " + metaData.tablename + " SET ");
328            List<String> modified = Lists.newArrayList(Iterables.filter(metaData.columnNames,new Predicate<String>() {
329
330                        @Override
331                        public boolean apply(String input) {
332                                return bean.isModified(input);
333                        }
334                }));
335            sql.append(Joiner.on(",").join(Lists.transform(modified,SQL_FUN1)));
336
337            sql.append(" WHERE ");
338            sql.append(Joiner.on(" AND ").join(
339                    Lists.transform(Arrays.asList(metaData.primaryKeyNames), SQL_FUN1)));
340            if(metaData.lockColumnType != null){
341                if(metaData.primaryKeyNames.length > 0){
342                        sql.append(" AND ");
343                }
344                checkArgument(metaData.lockColumnName != null, "NOT DEFINED lock column name");
345                sql.append(metaData.lockColumnName + "=?");
346            }
347            if(debug){
348                log("update : " + sql.toString());
349            }
350            ps = c.prepareStatement(sql.toString(),
351                                    ResultSet.TYPE_SCROLL_INSENSITIVE,
352                                    ResultSet.CONCUR_READ_ONLY);
353
354            int dirtyCount = this.fillPreparedStatement(ps, bean, SEARCH_EXACT,true);
355
356            if (dirtyCount == 0) {
357                if(debug){
358                        log("The bean to look is not initialized... do not update.");
359                }
360                return bean;
361            }
362            int[] pkIds = metaData.primaryKeyIds;
363            for(int i = 0; i<pkIds.length; ++i){
364                setPreparedStatement(ps, ++dirtyCount, bean, pkIds[i]);
365            }
366            if(metaData.lockColumnType != null){
367                setPreparedStatement(ps, ++dirtyCount, oldLockValue, metaData.columnIDOf(metaData.lockColumnName));
368            }
369            int rowCount = ps.executeUpdate();
370            if (metaData.lockColumnType != null && rowCount==0) {
371                throw new OptimisticLockingException("sql2java.exception.optimisticlock");
372            }
373            // listener callback
374            getListenerContainer().afterUpdate(bean); 
375            bean.resetIsModified();
376
377            return bean;
378        }
379        catch(SQLException e)
380        {
381            throw new RuntimeDaoException(new DataAccessException(e));
382        }
383        finally
384        {
385            // listener callback
386            getListenerContainer().done();
387            getManager().close(ps);
388            freeConnection(c);
389        }
390    }
391    
392    class ListAction implements TableManager.Action<B> {
393        final List<B> list;
394        public ListAction() {
395            list=new LinkedList<B>();
396        }
397
398        public List<B> getList() {
399            return list;
400        }
401
402        @Override
403        public void call(B bean) {
404            list.add(bean);
405        }
406
407    }
408    
409    @Override
410    public int countAll()throws RuntimeDaoException{
411        return this.countWhere("");
412    }
413
414    @Override
415    public int countUsingTemplate(B bean)throws RuntimeDaoException{
416        return this.countUsingTemplate(bean, SEARCH_EXACT);
417    }
418
419    @Override
420    public int deleteAll()throws RuntimeDaoException{
421        return this.deleteByWhere("");
422    }
423
424    @Override
425    public B[] loadAll()throws RuntimeDaoException{
426        return this.loadByWhere(null);
427    }
428
429    @Override
430    public int loadAll(TableManager.Action<B> action)throws RuntimeDaoException{
431        return this.loadByWhere(null, action);
432    }
433
434    @SuppressWarnings("unchecked")
435        @Override
436    public B[] loadAll(int startRow, int numRows)throws RuntimeDaoException{
437        return this.loadByWhereAsList(null, null, startRow, numRows).toArray((B[]) Array.newInstance(metaData.beanType, 0));
438    }
439
440    @Override
441    public int loadAll(int startRow, int numRows, TableManager.Action<B> action)throws RuntimeDaoException{
442        return this.loadByWhereForAction(null, null, startRow, numRows, action);
443    }
444
445    @Override
446    public List<B> loadAllAsList()throws RuntimeDaoException{
447        return this.loadUsingTemplateAsList(null,1, -1, SEARCH_EXACT);
448    }
449
450    @Override
451    public List<B> loadAllAsList(int startRow, int numRows)throws RuntimeDaoException{
452        return this.loadUsingTemplateAsList(null, startRow, numRows, SEARCH_EXACT);
453    }
454
455    @Override
456        public B loadByPrimaryKey(B bean)throws RuntimeDaoException{
457                return bean==null ? null : loadByPrimaryKey(bean.primaryValues());
458        }
459
460        @Override
461        public B loadByPrimaryKeyChecked(B bean)throws RuntimeDaoException,ObjectRetrievalException{
462            return loadByPrimaryKeyChecked(checkNotNull(bean,"bean is null").primaryValues());
463        }
464        @Override
465        public final B loadByPrimaryKeyChecked(Object ...keys)throws RuntimeDaoException,ObjectRetrievalException{
466                // {{check parameters
467                if(metaData.primaryKeyNames.length == 0){
468                        throw new UnsupportedOperationException();
469                }
470            String[] pkNames = metaData.primaryKeyNames;
471            int[] pkIds = metaData.primaryKeyIds;
472                if(hasNull(keys)){
473                        throw new ObjectRetrievalException(new NullPointerException("primary key must not be null"));
474                }
475                checkArgument(keys.length == pkNames.length,
476                                "INVALID ARGUMENT NUM, %s required",pkNames.length);
477            for(int i=0; i<pkNames.length; ++i){
478                Object key = keys[i];
479                Class<?> type = metaData.columnTypeOf(pkIds[i]);
480                checkArgument(type.isAssignableFrom(key.getClass()),
481                                "INVALID type for pk %s,%s required",pkNames[i],type.getName());
482            }
483         // }}check parameters
484            return doLoadByPrimaryKeyChecked(keys);
485        }
486        
487        protected B doLoadByPrimaryKeyChecked(Object ...keys)throws RuntimeDaoException,ObjectRetrievalException{
488                if(metaData.primaryKeyNames.length == 0){
489                        throw new UnsupportedOperationException();
490                }
491            String[] pkNames = metaData.primaryKeyNames;
492            int[] pkIds = metaData.primaryKeyIds;
493                if(hasNull(keys)){
494                        throw new ObjectRetrievalException(new NullPointerException("primary key must not be null"));
495                }
496                checkArgument(keys.length == pkNames.length,
497                                "INVALID ARGUMENT NUM, %s required",pkNames.length);
498            for(int i=0; i<pkNames.length; ++i){
499                Object key = keys[i];
500                Class<?> type = metaData.columnTypeOf(pkIds[i]);
501                checkArgument(type.isAssignableFrom(key.getClass()),
502                                "INVALID type for pk %s,%s required",pkNames[i],type.getName());
503            }
504            Connection c = null;
505            PreparedStatement ps = null;
506            try
507            {
508                c = this.getConnection();
509                StringBuilder sql = new StringBuilder("SELECT " + metaData.columnFields + " FROM " + metaData.tablename + " WHERE ");
510                sql.append(Joiner.on(" AND ").join(
511                            Lists.transform(Arrays.asList(pkNames), SQL_FUN1)));
512                if(debug){
513                        log("LOAD BY PK: " + sql.toString());
514                }
515                ps = c.prepareStatement(sql.toString(),
516                                        ResultSet.TYPE_SCROLL_INSENSITIVE,
517                                        ResultSet.CONCUR_READ_ONLY);
518                        for(int i = 0; i<pkNames.length; ++i){
519                                setPreparedStatement(ps,  i+1, keys[i], pkIds[i]);
520                        }
521                List<B> pReturn = this.loadByPreparedStatementAsList(ps,null,1,-1);
522                if (1 == pReturn.size()) {
523                    return pReturn.get(0);
524                } else {
525                    throw new ObjectRetrievalException();
526                }
527            }
528            catch(ObjectRetrievalException e)
529            {
530                throw e;
531            }
532            catch(SQLException e)
533            {
534                throw new RuntimeDaoException(new DataRetrievalException(e));
535            }
536            finally
537            {
538                this.getManager().close(ps);
539                this.freeConnection(c);
540            }
541        }
542
543        @Override
544        public B loadByPrimaryKey(Object ...keys)throws RuntimeDaoException{
545            try{
546                return loadByPrimaryKeyChecked(keys);
547            }catch(ObjectRetrievalException e){
548                // not found
549                return null;
550            }
551        }
552
553        protected <K> List<B> loadByPks(Collection<K> keys){
554                checkState(metaData.primaryKeyCount == 1,"UNSUPPORTED OPERATION");
555            if(null == keys){
556                return Collections.emptyList();
557            }
558            ArrayList<B> list = new ArrayList<B>(keys.size());
559            for(K key:keys){
560                list.add(loadByPrimaryKey(key));
561            }
562            return list;
563        }
564        
565        @SuppressWarnings("unchecked")
566        protected <K> List<B> loadByPks(K... keys){
567            if(null == keys){
568                return Collections.emptyList();
569            }
570                return loadByPks(Arrays.asList(keys));
571        }
572
573        @Override
574    public boolean existsByPrimaryKey(B bean)throws RuntimeDaoException{
575        return null == bean ? false : existsPrimaryKey(bean.primaryValues());
576    }
577    @Override
578    public B checkDuplicate(B bean)throws RuntimeDaoException,ObjectRetrievalException{
579        if(existsByPrimaryKey(bean)){
580            throw new ObjectRetrievalException("Duplicate entry ("+ bean.primaryValues() +") for key 'PRIMARY'");
581        }
582        return bean;   
583    }
584    @Override
585    public final boolean existsPrimaryKey(Object ...keys)throws RuntimeDaoException{
586        // {{check parameters
587        if(metaData.primaryKeyNames.length == 0){
588                throw new UnsupportedOperationException();
589        }
590        String[] pkNames = metaData.primaryKeyNames;
591        int[] pkIds = metaData.primaryKeyIds;
592        if(null == keys || hasNull(keys)){
593                return false;
594        }
595        checkArgument(keys.length == pkNames.length,
596                        "INVALID ARGUMENT NUM, %s required",pkNames.length);
597        for(int i=0; i<pkNames.length; ++i){
598                Object key = keys[i];
599                Class<?> type = metaData.columnTypeOf(pkIds[i]);
600                checkArgument(type.isInstance(key),
601                                "INVALID type for pk %s,%s required",pkNames[i],type.getName());
602        }
603        // }}check parameters
604        return doExistsPrimaryKey(keys);
605    }
606    protected boolean doExistsPrimaryKey(Object ...keys)throws RuntimeDaoException{
607        String[] pkNames = metaData.primaryKeyNames;
608        int[] pkIds = metaData.primaryKeyIds;
609        Connection c = null;
610        PreparedStatement ps = null;
611        try{
612            c = this.getConnection();
613            StringBuilder sql = new StringBuilder("SELECT COUNT(*) AS MCOUNT FROM "  + metaData.tablename + " WHERE ");
614            sql.append(Joiner.on(" AND ").join(Lists.transform(Arrays.asList(pkNames),SQL_FUN1)));
615            if(debug){
616                log("ExistsPrimaryKey: " + sql);
617            }
618            ps = c.prepareStatement(sql.toString(),
619                                    ResultSet.TYPE_SCROLL_INSENSITIVE,
620                                    ResultSet.CONCUR_READ_ONLY);
621
622                        for(int i = 0; i<pkNames.length; ++i){
623                                 setPreparedStatement(ps, i+1, keys[i], pkIds[i]);
624                        }
625            return 1 == getManager().runPreparedStatementForValue(Long.class, ps);
626        }catch(SQLException e){
627            throw new RuntimeDaoException(new ObjectRetrievalException(e));
628        }finally{
629            this.getManager().close(ps);
630            this.freeConnection(c);
631        }
632    }
633    protected <T>T checkDuplicateByPk(T primaryKeyValue)throws ObjectRetrievalException{
634        if(metaData.primaryKeyNames.length != 1){
635                throw new UnsupportedOperationException();
636        }
637            if(existsPrimaryKey(primaryKeyValue)){
638                throw new ObjectRetrievalException("Duplicate entry '"+ primaryKeyValue +"' for key 'PRIMARY'");
639            }
640            return primaryKeyValue;
641        }
642
643        @Override
644    public B[] loadByWhere(String where)throws RuntimeDaoException{
645        return this.loadByWhere(where, (int[])null);
646    }
647
648    @Override
649    public int loadByWhere(String where, TableManager.Action<B> action)throws RuntimeDaoException{
650        return this.loadByWhere(where, null, action);
651    }
652
653    @Override
654    public B[] loadByWhere(String where, int[] fieldList)throws RuntimeDaoException{
655        return this.loadByWhere(where, fieldList, 1, -1);
656    }
657
658    @Override
659    public int loadByWhere(String where, int[] fieldList, TableManager.Action<B> action)throws RuntimeDaoException{
660        return this.loadByWhere(where, fieldList, 1, -1, action);
661    }
662
663    @SuppressWarnings("unchecked")
664    @Override
665    public B[] loadByWhere(String where, int[] fieldList, int startRow, int numRows)throws RuntimeDaoException{
666        return this.loadByWhereAsList(where, fieldList, startRow, numRows).toArray((B[])Array.newInstance(metaData.beanType,0));
667    }
668
669    @Override
670    public int loadByWhere(String where, int[] fieldList, int startRow, int numRows,
671            TableManager.Action<B> action)throws RuntimeDaoException{
672        return this.loadByWhereForAction(where, fieldList, startRow, numRows, action);
673    }
674
675    @Override
676    public List<B> loadByWhereAsList(String where)throws RuntimeDaoException{
677        return this.loadByWhereAsList(where, null, 1, -1);
678    }
679
680    @Override
681    public List<B> loadByWhereAsList(String where, int[] fieldList)throws RuntimeDaoException{
682        return this.loadByWhereAsList(where, fieldList, 1, -1);
683    }
684
685    @Override
686    public List<B> loadByWhereAsList(String where, int[] fieldList, int startRow, int numRows)throws RuntimeDaoException{
687        ListAction action = new ListAction();
688        loadByWhereForAction(where, fieldList, startRow, numRows, action);              
689        return action.getList();
690    }
691
692    @Override
693    public int loadByWhereForAction(String where, int[] fieldList, int startRow, int numRows,TableManager.Action<B> action)throws RuntimeDaoException{
694        String sql=createSelectSql(fieldList, where);
695        return this.loadBySqlForAction(sql, null, fieldList, startRow, numRows, action);
696    }
697
698    @Override
699    public B[] loadUsingTemplate(B bean)throws RuntimeDaoException{
700        return this.loadUsingTemplate(bean, 1, -1, SEARCH_EXACT);
701    }
702
703    @Override
704    public int loadUsingTemplate(B bean, TableManager.Action<B> action)throws RuntimeDaoException{
705        return this.loadUsingTemplate(bean, null, 1, -1, SEARCH_EXACT, action);
706    }
707
708    @Override
709    public B[] loadUsingTemplate(B bean, int startRow, int numRows)throws RuntimeDaoException{
710        return this.loadUsingTemplate(bean, startRow, numRows, SEARCH_EXACT);
711    }
712
713    @Override
714    public int loadUsingTemplate(B bean, int startRow, int numRows,
715            TableManager.Action<B> action)throws RuntimeDaoException{
716        return this.loadUsingTemplate(bean, null, startRow, numRows,SEARCH_EXACT, action);
717    }
718
719    @SuppressWarnings("unchecked")
720    @Override
721    public B[] loadUsingTemplate(B bean, int startRow, int numRows, int searchType)throws RuntimeDaoException{
722        return this.loadUsingTemplateAsList(bean, startRow, numRows, searchType).toArray((B[])Array.newInstance(metaData.beanType,0));
723    }
724
725    @Override
726    public List<B> loadUsingTemplateAsList(B bean)throws RuntimeDaoException{
727        return this.loadUsingTemplateAsList(bean, 1, -1, SEARCH_EXACT);
728    }
729
730    @Override
731    public List<B> loadUsingTemplateAsList(B bean, int startRow, int numRows)throws RuntimeDaoException{
732        return this.loadUsingTemplateAsList(bean, startRow, numRows, SEARCH_EXACT);
733    }
734
735    @Override
736    public List<B> loadUsingTemplateAsList(B bean, int startRow, int numRows, int searchType)throws RuntimeDaoException{
737        ListAction action = new ListAction();
738        loadUsingTemplate(bean,null,startRow,numRows,searchType, action);
739        return action.getList();
740    }
741    //18
742    @Override
743    public B loadUniqueUsingTemplate(B bean)
744    {
745        try {
746                return loadUniqueUsingTemplateChecked(bean);    
747                } catch (ObjectRetrievalException e) {
748                        return null;
749                }
750     }
751    //18-1
752    @Override
753    public B loadUniqueUsingTemplateChecked(B bean) throws ObjectRetrievalException
754    {
755         List<B> beans = this.loadUsingTemplateAsList(bean);
756         switch(beans.size()){
757         case 0:
758             throw new ObjectRetrievalException("Not found element !!");
759         case 1:
760             return beans.get(0);
761         default:
762             throw new RuntimeDaoException(new ObjectRetrievalException("More than one element !!"));
763         }
764     }
765    
766    @Override
767    public int loadUsingTemplate(B bean, int[] fieldList, int startRow, int numRows,int searchType, Action<B> action)
768    {
769        StringBuilder sqlWhere = new StringBuilder("");
770        String sql=createSelectSql(fieldList,
771                        this.fillWhere(sqlWhere, bean, searchType) > 0 ? " WHERE "+ sqlWhere.toString() : null);
772        PreparedStatement ps = null;
773        Connection connection = null;
774        try {
775            connection = this.getConnection();
776            if(debug){
777                log("loadUsingTemplate:" + sql);
778            }
779            ps = connection.prepareStatement(sql,
780                    ResultSet.TYPE_FORWARD_ONLY,
781                    ResultSet.CONCUR_READ_ONLY);
782            this.fillPreparedStatement(ps, bean, searchType,false);
783            return this.loadByPreparedStatement(ps, fieldList, startRow, numRows, action);
784        } catch (DaoException e) {
785            throw new RuntimeDaoException(e);
786        }catch (SQLException e) {
787            throw new RuntimeDaoException(new DataAccessException(e));
788        } finally {
789            this.getManager().close(ps);
790            this.freeConnection(connection);
791        }
792    }
793    /**
794     * @param <F> bean type of foreign table 
795     * @param left
796     * @param columnsMap
797     * @param startRow
798     * @param numRows
799     * @return list of B or empty list
800     */
801    private <F extends BaseBean>
802        List<B> loadByForeignKeyAsList(F left,Map<Integer,Integer>columnsMap,int startRow, int numRows)
803        {
804            if(null == left){
805                return Collections.emptyList();
806            }
807                checkArgument(columnsMap != null && !columnsMap.isEmpty(),"columnsMap is null or empty");
808            B bean = createBean().copy(left, columnsMap);
809            return loadUsingTemplateAsList(bean,startRow,numRows);
810        }
811    /**
812     * @param <F> bean type of foreign table
813     * @param left
814     * @param fkName
815     * @param startRow
816     * @param numRows
817     * @return list of B or empty list
818     */
819    public <F extends BaseBean>
820        List<B> loadByForeignKeyAsList(F left,String fkName,int startRow, int numRows)
821        {
822            ImmutableBiMap<Integer, Integer> columnsMap = metaData.foreignKeyIdMapOf(fkName).inverse();
823            return loadByForeignKeyAsList(left,columnsMap,startRow,numRows);
824        }
825
826        @Override
827    public B save(B bean)throws RuntimeDaoException{
828        if(null != bean){
829            if (bean.isNew()) {
830                this.insert(bean);
831            } else {
832                this.update(bean);
833            }
834        }
835        return bean;
836    }
837    
838    @Override
839    public B[] save(B[] beans)throws RuntimeDaoException{
840        if(null != beans){
841            for (B bean : beans) 
842            {
843                this.save(bean);
844            }
845        }
846        return beans;
847    }
848
849    @Override
850    public <C extends Collection<B>> C save(C beans)throws RuntimeDaoException{
851        if(null != beans){
852            for (B bean : beans) 
853            {
854                this.save(bean);
855            }
856        }
857        return beans;
858    }
859    
860    @Override
861    public <C extends Collection<B>> C saveAsTransaction(final C beans)throws RuntimeDaoException{
862        return this.runAsTransaction(new Callable<C>(){
863            @Override
864            public C call() throws Exception {
865                return save(beans);
866            }});
867    }
868
869    @Override
870    public B[] saveAsTransaction(final B[] beans)throws RuntimeDaoException{
871        return this.runAsTransaction(new Callable<B[]>(){
872            @Override
873            public B[] call() throws Exception {
874                return save(beans);
875            }});
876    }
877
878    private String checkWhere(String where){
879        where = MoreObjects.firstNonNull(where, "").trim();
880        checkArgument(where.isEmpty() || where.toUpperCase().startsWith("WHERE "),
881                        "WHERE expression must start with 'WHERE'(case insensitive)");
882        return where;
883    }
884    
885    @SuppressWarnings("unchecked")
886        @Override
887    public <T> List<T> loadColumnAsList(String column,boolean distinct,String where,int startRow,int numRows)throws RuntimeDaoException{
888        int columnId = metaData.columnIDOf(column);
889        checkArgument(columnId>=0,"INVALID column name %s",column);
890        String sql = String.format("SELECT %s " + metaData.columnNameOf(columnId) + " FROM %s %s",
891                distinct ? "DISTINCT" : "",
892                metaData.tablename,
893                checkWhere(where));
894        return runSqlAsList((Class<T>)metaData.columnTypes.get(columnId), sql);
895    }
896    /**
897     * generate SQL query(SELECT) statement,such as: 'SELECT id,name from mytable WHERE id=1'
898     * @param fieldList
899     * @param where where condition expression statement that start with 'WHERE',or {@code null},or empty string
900     * @return SQL statement string
901     * @throws IllegalArgumentException where condition expression don't start with 'WHERE'
902     */
903    private String createSelectSql(int[] fieldList, String where){
904        StringBuffer sql = new StringBuffer(128);
905        String fullFields = metaData.columnFullFields;
906        if(null == fieldList || 0 == fieldList.length) {
907            sql.append("SELECT ").append(fullFields);
908        } else{
909            sql.append("SELECT ");
910            String[] names=fullFields.split(",");
911            for(int i = 0; i < fieldList.length; ++i){
912                if(i > 0) {
913                    sql.append(",");
914                }
915                sql.append(names[fieldList[i]]);
916            }      
917        }
918        sql.append(" FROM " + this.metaData.tablename + " ");
919        sql.append(checkWhere(where));
920        return sql.toString();
921    }
922
923    //2.2
924
925    @Override
926    public int delete(B bean){
927        if(metaData.primaryKeyNames.length==0){
928                throw new UnsupportedOperationException();
929        }
930        if(hasNullPk(bean)){
931                return 0;
932        }
933        Connection c = null;
934        PreparedStatement ps = null;
935        String [] pks = metaData.primaryKeyNames;
936        int[] pkIds = metaData.primaryKeyIds;
937        try
938        {
939            // listener callback
940            getListenerContainer().beforeDelete(bean);
941            c = this.getConnection();
942            StringBuilder sql = new StringBuilder("DELETE  FROM " + metaData.tablename +" WHERE ");
943            sql.append(Joiner.on(" AND ").join(Lists.transform(Arrays.asList(pks),SQL_FUN1)));
944            if(debug){
945                log("deleteByPrimaryKey: " + sql);
946            }
947            ps = c.prepareStatement(sql.toString(),
948                                    ResultSet.TYPE_SCROLL_INSENSITIVE,
949                                    ResultSet.CONCUR_READ_ONLY);
950            for(int i = 0; i<pkIds.length; ++i){
951                 setPreparedStatement(ps, i+1, bean, pkIds[i]);
952            }
953            int rows=ps.executeUpdate();
954            if(rows>0){
955                // listener callback
956                getListenerContainer().afterDelete(bean);
957            }
958            return rows;
959        }
960        catch(SQLException e)
961        {
962            throw new RuntimeDaoException(new DataAccessException(e));
963        }
964        finally
965        {
966            // listener callback
967            getListenerContainer().done();
968            getManager().close(ps);
969            freeConnection(c);
970        }
971    }
972
973    protected <K> int deleteByPks(Collection<K> keys){
974                checkState(metaData.primaryKeyCount == 1,"UNSUPPORTED OPERATION");
975            int count = 0;
976            if(null != keys){
977                for(K key :keys){
978                    count += deleteByPrimaryKey(key);
979                }
980            }
981            return count;
982        }
983
984        @SuppressWarnings("unchecked")
985        protected <K> int deleteByPks(K... keys){
986            if(null == keys){
987                return 0;
988            }
989            return deleteByPks(Arrays.asList(keys));
990        }
991
992        @Override
993        public int deleteByPrimaryKey(Object ...keys)throws RuntimeDaoException{
994            if(hasNull(keys)){
995                return 0;
996            }
997                checkArgument(keys.length == metaData.primaryKeyCount,"argument number mismatch with primary key number");
998            return delete(createBean(keys));
999        }
1000
1001        //2.4 
1002
1003        @SuppressWarnings("unchecked")
1004        @Override
1005        public int delete(B... beans)throws RuntimeDaoException{
1006            int count = 0;
1007            if(null != beans){
1008                for(B bean :beans){
1009                    count += delete(bean);
1010                }
1011            }
1012            return count;
1013        }
1014
1015        //2.5  
1016
1017        @Override
1018        public int delete(Collection<B> beans)throws RuntimeDaoException{
1019            int count = 0;
1020            if(null != beans){
1021                for(B bean :beans){
1022                    count += delete(bean);
1023                }
1024            }
1025            return count;
1026        }
1027
1028        @SuppressWarnings("unchecked")
1029    @Override
1030        public <T extends BaseBean> T getReferencedBean(B bean, String fkName) throws RuntimeDaoException{
1031        if(null == bean){
1032                return null;
1033        }
1034        ForeignKeyMetaData foreignkey = checkNotNull(metaData.foreignKeys.get(fkName),
1035                        "INVALID fkName %s for table %s", fkName, metaData.tablename);
1036                BaseTableManager<T> foreignManager = (BaseTableManager<T>)managerOf(foreignkey.foreignTable);
1037        T t = foreignManager.createBean().copy(bean, metaData.foreignKeyIdMapOf(fkName));
1038        return foreignManager.loadByPrimaryKey(t);
1039    }
1040    
1041    @SuppressWarnings("unchecked")
1042    @Override
1043        public <T extends BaseBean> T setReferencedBean(B bean, T beanToSet, String fkName) throws RuntimeDaoException{
1044        if(null != bean){
1045                ForeignKeyMetaData foreignkey = checkNotNull(metaData.foreignKeys.get(fkName),
1046                                "INVALID fkName %s for table %s", fkName, metaData.tablename);
1047                BaseTableManager<T> foreignManager = (BaseTableManager<T>)managerOf(foreignkey.foreignTable);
1048                foreignManager.save(beanToSet);
1049                bean.copy(beanToSet, metaData.foreignKeyIdMapOf(fkName).inverse());
1050        }
1051        return beanToSet;
1052    }
1053    @SuppressWarnings("unchecked")
1054    @Override
1055        public <T extends BaseBean> T[] getImportedBeans(B bean,String fkName) throws RuntimeDaoException{
1056        ForeignKeyMetaData foreignkey =metaData.getImportedKey(fkName);
1057        RowMetaData foreignMetaData = RowMetaData.getMedaData(foreignkey.foreignTable);
1058        return getImportedBeansAsList(bean,fkName).toArray((T[]) Array.newInstance(foreignMetaData.beanType, 0));
1059    }
1060
1061    @SuppressWarnings("unchecked")
1062    @Override
1063        public <T extends BaseBean> List<T> getImportedBeansAsList(B bean, String fkName)throws RuntimeDaoException{
1064        ForeignKeyMetaData foreignkey =metaData.getImportedKey(fkName);
1065                BaseTableManager<T> foreignManager = (BaseTableManager<T>)managerOf(foreignkey.ownerTable);
1066                return foreignManager.loadByForeignKeyAsList(bean,fkName, 1, -1);
1067    }
1068    protected <T extends BaseBean> List<T> getImportedBeansAsList(String fkName,Object...keys) throws RuntimeDaoException{          
1069        return getImportedBeansAsList(createBean(keys),fkName);
1070    }
1071        @SuppressWarnings("unchecked")
1072        protected <T extends BaseBean> T[] getImportedBeans(String fkName,Object...keys) throws RuntimeDaoException{              
1073        ForeignKeyMetaData foreignkey =metaData.getImportedKey(fkName);
1074        RowMetaData foreignMetaData = RowMetaData.getMedaData(foreignkey.foreignTable);
1075        return getImportedBeansAsList(fkName,keys).toArray((T[]) Array.newInstance(foreignMetaData.beanType, 0));
1076    }
1077    @SuppressWarnings("unchecked")
1078    @Override
1079        public <T extends BaseBean, C extends Collection<T>> C setImportedBeans(B bean, C importedBeans,
1080            String fkName)throws RuntimeDaoException{
1081        if(null != importedBeans){
1082                ForeignKeyMetaData foreignkey =metaData.getImportedKey(fkName);
1083                BaseTableManager<T> foreignManager = (BaseTableManager<T>)managerOf(foreignkey.ownerTable);
1084                for( T importBean : importedBeans ){
1085                        foreignManager.setReferencedBean(importBean , bean,fkName);
1086            }
1087        }
1088        return importedBeans;
1089    }
1090    
1091    @Override
1092    public <T extends BaseBean> T[] setImportedBeans(B bean, T[] importedBeans, String fkName) throws RuntimeDaoException{
1093        if(null != importedBeans){
1094                setImportedBeans(bean, Arrays.asList(importedBeans), fkName);
1095        }
1096        return importedBeans;
1097    }
1098    @SuppressWarnings("unchecked")
1099    @Override
1100        public int deleteImportedBeans(B bean,String fkName){
1101        if(bean == null){
1102                return 0;
1103        }
1104        ForeignKeyMetaData foreignkey = metaData.getImportedKey(fkName);
1105        RowMetaData foreignMetaData = RowMetaData.getMedaData(foreignkey.foreignTable);
1106                BaseTableManager<BaseBean> foreignManager = (BaseTableManager<BaseBean>)managerOf(foreignkey.ownerTable);
1107                BaseBean tmpl = foreignManager.createBean().copy(bean,foreignMetaData.foreignKeyIdMapOf(fkName));
1108                return foreignManager.deleteUsingTemplate(tmpl);
1109    }
1110    protected int deleteImportedBeans(Map<Integer, Object> idValueMap, String fkName) throws RuntimeDaoException{         
1111        B bean = createBean().copy(idValueMap);
1112        return deleteImportedBeans(bean,fkName);
1113    }
1114    
1115    protected int deleteImportedBeans(String fkName,Object...keys) throws RuntimeDaoException{          
1116        return deleteImportedBeans(createBean(keys),fkName);
1117    }
1118    private Map<Integer, Object> makeIndexValueMap(String indexName,Object ...indexValues){
1119        IndexMetaData indexMetaData = metaData.indices.get(indexName);
1120        checkArgument(null != indexMetaData,"INVALID index name %s",indexName);
1121        checkArgument(null != indexValues && indexValues.length == indexMetaData.columns.size(),"INVALID index value");
1122        ImmutableMap.Builder<Integer,Object> builder = ImmutableMap.<Integer,Object>builder();
1123        for(int i=0;i<indexValues.length;++i){
1124                Object value = indexValues[i];
1125                if(null != value){
1126                        String column = indexMetaData.columns.get(i);
1127                        Class<?> columnType = metaData.columnTypeOf(column);
1128                        checkArgument(columnType.isInstance(value),
1129                                        "INVALID value type %s for %s,%s required",value.getClass(),column,columnType);
1130                        builder.put(metaData.columnIDOf(column), value);
1131                }
1132        }
1133        return builder.build();
1134    }
1135    //2.4 override IDeviceManager
1136
1137    @SuppressWarnings("unchecked")
1138    @Override
1139    public B[] loadByIndex(String indexName,Object ...keys)throws RuntimeDaoException{
1140        return this.loadByIndexAsList(indexName,keys).toArray((B[])Array.newInstance(metaData.beanType,0));
1141    }
1142    @Override
1143    public List<B> loadByIndexAsList(String indexName,Object ...indexValues)throws RuntimeDaoException{
1144        Map<Integer, Object> map = makeIndexValueMap(indexName,indexValues);
1145        return loadUsingTemplateAsList(map);
1146    }
1147    @Override
1148    public final B loadUniqueByIndex(String indexName,Object ...indexValues)throws RuntimeDaoException{
1149        return doLoadUniqueByIndex(indexName, indexValues);
1150    }
1151    protected B doLoadUniqueByIndex(String indexName,Object ...indexValues)throws RuntimeDaoException{
1152        Map<Integer, Object> keys = makeIndexValueMap(indexName,indexValues);
1153        B bean = createBean().copy(keys);
1154        return loadUniqueUsingTemplate(bean);
1155    }
1156    @Override
1157    public final B loadUniqueByIndexChecked(String indexName,Object ...indexValues)throws ObjectRetrievalException{
1158                if(hasNull(indexValues)){
1159                        throw new ObjectRetrievalException(new NullPointerException("index keys must not be null"));
1160                }
1161        return doLoadUniqueByIndexChecked(indexName, indexValues);
1162    }
1163    protected B doLoadUniqueByIndexChecked(String indexName,Object ...indexValues)throws ObjectRetrievalException{
1164        Map<Integer, Object> keys = makeIndexValueMap(indexName,indexValues);
1165        B bean = createBean().copy(keys);
1166        return loadUniqueUsingTemplateChecked(bean);
1167    }
1168    @Override
1169    public int deleteByIndex(String indexName,Object ...indexValues)throws RuntimeDaoException{
1170        Map<Integer, Object> map = makeIndexValueMap(indexName,indexValues);
1171        return deleteUsingTemplate(map);
1172    }
1173    
1174    private List<B> loadUsingTemplateAsList(Map<Integer, Object>keys){
1175        B bean = createBean().copy(keys);
1176        return loadUsingTemplateAsList(bean);
1177    }
1178    
1179    private int deleteUsingTemplate(Map<Integer, Object>keys){
1180        B bean = createBean().copy(keys);
1181        return deleteUsingTemplate(bean);
1182    }
1183    protected <T>List<B> loadByIndexForIndices(String indexName,Collection<T> indexs)
1184    {
1185        IndexMetaData indexMetaData = metaData.indices.get(indexName);
1186        checkArgument(null != indexMetaData,"INVALID index name %s",indexName);
1187        checkArgument(1 == indexMetaData.columns.size(),"column count of  index must be 1");
1188        checkArgument(indexMetaData.unique,"index must be unique");
1189        if(null == indexs ){
1190            return Collections.emptyList();
1191        }
1192        List<B> list = new ArrayList<B>(indexs.size());
1193        for(T key: indexs){
1194            list.add(loadUniqueByIndex(indexName,key));
1195        }
1196        return list;
1197    }
1198    @SuppressWarnings("unchecked")
1199        protected <T>List<B> loadByIndexForIndices(String indexName,T... indexs){
1200        if(indexs == null || indexs.length == 0){
1201                return Collections.emptyList();
1202        }
1203        return loadByIndexForIndices(indexName,Arrays.asList(indexs));
1204    }
1205    protected <T>int deleteByIndexForIndices(String indexName,Collection<T> indexs)
1206    {
1207        IndexMetaData indexMetaData = metaData.indices.get(indexName);
1208        checkArgument(null != indexMetaData,"INVALID index name %s",indexName);
1209        checkArgument(1 == indexMetaData.columns.size(),"column count of  index must be 1");
1210
1211        if(null == indexs ){
1212            return 0;
1213        }
1214        int count = 0;
1215        for(T key: indexs){
1216                if(key != null){
1217                        count += deleteByIndex(indexName, key);
1218                }
1219        }
1220        return count;
1221    }
1222    @SuppressWarnings("unchecked")
1223        protected <T>int deleteByIndexForIndices(String indexName,T... indexs){
1224        if(indexs == null || indexs.length == 0){
1225                return 0;
1226        }
1227        return deleteByIndexForIndices(indexName,Arrays.asList(indexs));
1228    }
1229    private boolean checkPkValid(B bean)
1230        {
1231                if(metaData.primaryKeyNames.length ==0){
1232                        return false;
1233                }
1234        for(String name:metaData.primaryKeyNames){
1235                if(! (bean.isInitialized(name) && null != bean.getValue(name))){
1236                        return false;   
1237                }
1238        }       
1239        return true;
1240        }
1241    //21
1242
1243    @Override
1244    public int deleteUsingTemplate(B bean)
1245    {
1246        if(bean == null || !bean.isModified()){
1247                return 0;
1248        }
1249        if(checkPkValid(bean)){
1250            return this.deleteByPrimaryKey(bean);
1251        }
1252        if( !getListenerContainer().isEmpty()){
1253            final DeleteBeanAction action=new DeleteBeanAction(); 
1254            this.loadUsingTemplate(bean,action);
1255            return action.getCount();
1256        }
1257        Connection c = null;
1258        PreparedStatement ps = null;
1259        StringBuilder sql = new StringBuilder("DELETE FROM " + metaData.tablename +" ");
1260        StringBuilder sqlWhere = new StringBuilder("");
1261
1262        try
1263        {
1264            fillWhere(sqlWhere, bean, SEARCH_EXACT);
1265            sql.append(" WHERE ").append(sqlWhere);
1266            c = this.getConnection();
1267            if(debug){
1268                log("deleteUsingTemplate: " + sql.toString());
1269            }
1270            ps = c.prepareStatement(sql.toString(),
1271                                    ResultSet.TYPE_SCROLL_INSENSITIVE,
1272                                    ResultSet.CONCUR_READ_ONLY);
1273            this.fillPreparedStatement(ps, bean, SEARCH_EXACT, false);
1274
1275            return ps.executeUpdate();
1276        }
1277        catch(SQLException e)
1278        {
1279            throw new RuntimeDaoException(new DataAccessException(e));
1280        }
1281        finally
1282        {
1283            this.getManager().close(ps);
1284            this.freeConnection(c);
1285        }
1286    }
1287    
1288    //11
1289
1290    @Override
1291    public int deleteByWhere(String where)
1292    {
1293        if( !getListenerContainer().isEmpty()){
1294            final DeleteBeanAction action = new DeleteBeanAction(); 
1295            this.loadByWhere(where,action);
1296            return action.getCount();
1297        }
1298        Connection c = null;
1299        PreparedStatement ps = null;
1300
1301        try
1302        {
1303            c = this.getConnection();
1304            StringBuilder sql = new StringBuilder("DELETE FROM " + metaData.tablename + " " + where);
1305            if(debug){
1306                log("deleteByWhere: " + sql);
1307            }
1308            ps = c.prepareStatement(sql.toString());
1309            return ps.executeUpdate();
1310        }
1311        catch(SQLException e)
1312        {
1313            throw new RuntimeDaoException(new DataAccessException(e));
1314        }
1315        finally
1316        {
1317            this.getManager().close(ps);
1318            this.freeConnection(c);
1319        }
1320    }
1321    private void setPreparedStatement(PreparedStatement ps,int pos,Object value,int columnId)
1322                throws SQLException {
1323        Manager.setPreparedStatement(ps, pos, value, metaData.sqlTypes[columnId]);
1324    }
1325    private void setPreparedStatement(PreparedStatement ps,int pos,B bean,int columnId)
1326                throws SQLException {
1327        setPreparedStatement(ps,pos,bean.getValue(columnId),columnId);
1328    }
1329
1330    @Override
1331        public B save(B bean,Map<String, BaseBean> referenceBeans,Map<String, Collection<BaseBean>> importedBeans) throws RuntimeDaoException{
1332            if(null == bean){
1333                return null;
1334            }
1335            referenceBeans = MoreObjects.firstNonNull(referenceBeans, Collections.<String, BaseBean>emptyMap());
1336            importedBeans = MoreObjects.firstNonNull(importedBeans, Collections.<String, Collection<BaseBean>>emptyMap());
1337            for(Entry<String, BaseBean> entry:referenceBeans.entrySet()){
1338                BaseBean beanToSet = entry.getValue();
1339                if(beanToSet != null){
1340                        setReferencedBean(bean, beanToSet, entry.getKey());
1341                }
1342            }
1343            bean = this.save( bean );
1344            for(Entry<String, Collection<BaseBean>> entry:importedBeans.entrySet()){
1345                String ikName = entry.getKey();
1346                Collection<BaseBean> importeds = entry.getValue(); 
1347                if(null != importeds){
1348                        setImportedBeans(bean, importeds, ikName);
1349                        TableManager<BaseBean> impManager = managerOf(metaData.getImportedKey(ikName).ownerTable);
1350                        impManager.save(importeds);
1351                }
1352            }
1353                return bean;            
1354        }
1355    
1356    @Override
1357    public B saveAsTransaction(final B bean,final Map<String, BaseBean> referenceBeans,final Map<String, Collection<BaseBean>> importedBeans)throws RuntimeDaoException{
1358        return this.runAsTransaction(new Callable<B>(){
1359            @Override
1360            public B call() throws Exception {
1361                return save(bean , referenceBeans, importedBeans );
1362            }});
1363    }
1364    
1365    //_____________________________________________________________________
1366    //
1367    // COUNT
1368    //_____________________________________________________________________
1369    //25
1370
1371    @Override
1372    public int countWhere(String where)
1373    {
1374        String sql = new StringBuffer("SELECT COUNT(*) AS MCOUNT FROM " + metaData.tablename + " ")
1375                    .append(null == where ? "" : where).toString();
1376        return runSqlForValue(Long.class, sql).intValue();
1377    }
1378    //20
1379    /**
1380     * count the number of elements of a specific B bean given the search type
1381     *
1382     * @param bean the B template to look for
1383     * @param searchType exact ?  like ? starting like ?
1384     * @return the number of rows returned
1385     */
1386    @Override
1387    public int countUsingTemplate(B bean, int searchType)
1388    {
1389        Connection c = null;
1390        PreparedStatement ps = null;
1391        StringBuilder sql = new StringBuilder("SELECT COUNT(*) AS MCOUNT FROM " + metaData.tablename);
1392        StringBuilder sqlWhere = new StringBuilder("");
1393
1394        try
1395        {
1396            if (this.fillWhere(sqlWhere, bean, SEARCH_EXACT) > 0) {
1397                sql.append(" WHERE ").append(sqlWhere);
1398            } else {
1399                if(debug){
1400                        log("The bean to look is not initialized... counting all...");
1401                }
1402            }
1403
1404            c = this.getConnection();
1405            if(debug){
1406                log("countUsingTemplate: " + sql.toString());
1407            }
1408            ps = c.prepareStatement(sql.toString(),
1409                                    ResultSet.TYPE_SCROLL_INSENSITIVE,
1410                                    ResultSet.CONCUR_READ_ONLY);
1411            this.fillPreparedStatement(ps, bean, searchType,false);
1412            return getManager().runPreparedStatementForValue(Long.class,ps).intValue();
1413        }
1414        catch(SQLException e)
1415        {
1416            throw new RuntimeDaoException(new DataAccessException(e));
1417        }
1418        finally
1419        {
1420            this.getManager().close(ps);
1421            this.freeConnection(c);
1422            sql = null;
1423            sqlWhere = null;
1424        }
1425    }
1426    /**
1427     * fills the given StringBuilder with the sql where clauses constructed using the bean and the search type
1428     * @param sqlWhere the StringBuilder that will be filled
1429     * @param bean the bean to use for creating the where clauses
1430     * @param searchType exact ?  like ? starting like ?
1431     * @return the number of clauses returned
1432     */
1433    private int fillWhere(StringBuilder sqlWhere, B bean, int searchType)
1434    {
1435        if (bean == null) {
1436                return 0;
1437        }
1438        int dirtyCount = 0;
1439        String sqlEqualsOperation = searchType == SEARCH_EXACT ? "=" : " like ";
1440        List<String> fields = metaData.columnNames;
1441        for(int i=0; i<fields.size(); ++i){
1442                if(bean.isModified(i)){
1443                        ++dirtyCount;
1444                        if(bean.getValue(i) == null){
1445                                sqlWhere.append((sqlWhere.length() == 0) ? " " : " AND ").append(metaData.columnNameOf(i)).append(" IS NULL");
1446                        }else{
1447                                if(metaData.columnTypeOf(i) == String.class){
1448                                        sqlWhere.append((sqlWhere.length() == 0) ? " " : " AND ").append(metaData.columnNameOf(i)).append(" ").append(sqlEqualsOperation).append("?");
1449                                } else{
1450                                        sqlWhere.append((sqlWhere.length() == 0) ? " " : " AND ").append(metaData.columnNameOf(i)).append(" = ?");
1451                                }
1452                        }
1453                }
1454        }
1455
1456        return dirtyCount;
1457    }
1458    /**
1459     * fill the given prepared statement with the bean values and a search type
1460     * @param ps the PreparedStatement that will be filled
1461     * @param bean the bean to use for creating the where clauses
1462     * @param searchType exact ?  like ? starting like ?
1463     * @param fillNull wether fill null for null field
1464     * @return the number of clauses returned
1465     */
1466    private int fillPreparedStatement(PreparedStatement ps, B bean, int searchType,boolean fillNull) throws DaoException
1467    {
1468        if (bean == null) {
1469            return 0;
1470        }
1471        int dirtyCount = 0;
1472        try
1473        {
1474                List<String> fields = metaData.columnNames;
1475                for(int columnId=0; columnId<fields.size(); ++columnId){
1476                        Object value = bean.getValue(columnId);
1477                        if(null != value || fillNull){
1478                                if(bean.isModified(columnId)){
1479                                        if(String.class == metaData.columnTypeOf(columnId)){
1480                                                switch (searchType) {
1481                                                case SEARCH_EXACT:
1482                                                        setPreparedStatement(ps, ++dirtyCount , value, columnId);
1483                                                        break;
1484                                                case SEARCH_LIKE:
1485                                                        setPreparedStatement(ps, ++dirtyCount , SQL_LIKE_WILDCARD + value + SQL_LIKE_WILDCARD, columnId);
1486                                                        break;
1487                                                case SEARCH_STARTING_LIKE:
1488                                                        setPreparedStatement(ps, ++dirtyCount , SQL_LIKE_WILDCARD + value, columnId);
1489                                                        break;
1490                                                case SEARCH_ENDING_LIKE:
1491                                                        setPreparedStatement(ps, ++dirtyCount , value + SQL_LIKE_WILDCARD, columnId);
1492                                                        break;
1493                                                default:
1494                                                        throw new DaoException("Unknown search type : " + searchType);
1495                                                }
1496                                        }else{
1497                                                setPreparedStatement(ps, ++dirtyCount , bean, columnId);
1498                                        } 
1499                                }                               
1500                        }
1501                }
1502        }
1503        catch(SQLException e)
1504        {
1505            throw new DataAccessException(e);
1506        }
1507        return dirtyCount;
1508    }
1509    
1510    /**
1511     * 检查指定的字段是否为{@code null}
1512     * @author guyadong
1513     *
1514     */
1515    private static class FieldNullChecker implements Predicate<String>{
1516
1517                private final BaseBean bean;
1518
1519                public FieldNullChecker(BaseBean bean) {
1520                        this.bean = checkNotNull(bean,"bean is null");
1521                }
1522
1523                @Override
1524                public boolean apply(String input) {
1525                        return null == bean.getValue(input);
1526                }
1527        
1528    }
1529    //_____________________________________________________________________
1530    //
1531    // MANY TO MANY: LOAD OTHER BEAN VIA JUNCTION TABLE
1532    //_____________________________________________________________________
1533
1534    /**
1535     * Retrieves an list of R using the junction table junction table(junctionTablename), given a linked table bean, 
1536     * specifying the start row and the number of rows.
1537     * @param <L>
1538     * @param <R>
1539     * @param left
1540     * @param rightType
1541     * @param startRow the start row to be used (first row = 1, last row = -1)
1542     * @param numRows the number of rows to be retrieved (all rows = a negative number)
1543     * @return a list of R
1544     */
1545        @SuppressWarnings("unchecked")
1546        public <L extends BaseBean,R extends BaseBean>
1547    List<R> loadViaJunctionTableAsList( L left,Class<R> rightType, int startRow, int numRows)
1548    {
1549        checkState(!metaData.getJunctionTablePkMap().isEmpty(),"%s is not junction table",metaData.tablename);
1550        checkArgument(null != left,"left is null");
1551        checkArgument(null != rightType,"rightType is null");
1552        RowMetaData leftMetaData = RowMetaData.getMedaData(left.tableName());
1553        RowMetaData rightMetaData = RowMetaData.getMedaData(rightType);
1554        checkArgument(!leftMetaData.equals(rightMetaData),"same metadata  FOR left AND right type");
1555        checkArgument(metaData.isLinkedTable(leftMetaData.tablename),"INVALID left type %s",left.getClass());
1556        checkArgument(metaData.isLinkedTable(rightMetaData.tablename),"INVALID right type %s",rightType);
1557                Map<String, String> leftFields = metaData.junctionMapOf(leftMetaData.tablename);
1558        Map<String, String> rightFields = metaData.junctionMapOf(rightMetaData.tablename);
1559        if(Iterables.tryFind(leftFields.values(), new FieldNullChecker(left)).isPresent()){
1560                return Collections.emptyList();
1561        }
1562        Connection c = null;
1563        PreparedStatement ps = null;
1564        String sql = " SELECT " + rightMetaData.columnFullFields
1565                        + " FROM "+ metaData.tablename + ", " + rightMetaData.tablename
1566                        + " WHERE "
1567                        + Joiner.on(" AND ").join(Iterables.transform(leftFields.keySet(), 
1568                                        new Function<String,String>(){
1569                                                @Override
1570                                                public String apply(String input) {
1571                                                        return input + "=?";
1572                                                }}))
1573                        + " AND "
1574                        + Joiner.on(" AND ").join(Iterables.transform(rightFields.entrySet(), 
1575                                        new Function<Map.Entry<String,String>,String>(){
1576                
1577                                                                        @Override
1578                                                                        public String apply(Map.Entry<String, String> input) {
1579                                                                                return input.getKey() + "=" + input.getValue();
1580                                                                        }}));
1581        
1582        try
1583        {
1584            c = this.getConnection();
1585            if(debug){
1586                log("loadViaJunctionTableAsList" + sql);
1587            }
1588            ps = c.prepareStatement(sql,
1589                                    ResultSet.TYPE_SCROLL_INSENSITIVE,
1590                                    ResultSet.CONCUR_READ_ONLY);
1591            int pos =1;
1592            for(String key:leftFields.keySet()){
1593                String fullName = leftFields.get(key);
1594                int columnId = leftMetaData.columnIDOf(fullName);
1595                Manager.setPreparedStatement(ps, pos++, 
1596                                left.getValue(columnId), 
1597                                leftMetaData.sqlTypeOf(columnId));      
1598            }
1599            return ((BaseTableManager<R>) managerOf(rightMetaData.beanType))
1600                        .loadByPreparedStatementAsList(ps, null, startRow, numRows);
1601        }
1602        catch (SQLException e)
1603        {
1604            throw new RuntimeDaoException(new DaoException(e.getMessage(), e));
1605        }
1606        finally
1607        {
1608           this.getManager().close(ps);
1609           this.freeConnection(c);
1610        }
1611    }
1612
1613    private <L extends BaseBean,R extends BaseBean> B makeJunctionBean(L left,R right){
1614        Map<String, Object[]> map = metaData.getJunctionTablePkMap();
1615        checkState(!map.isEmpty(), "%s is not junction table",metaData.tablename);
1616        Object[] primaryValues = new Object[metaData.primaryKeyCount];
1617                for(int i=0; i<primaryValues.length; ++i){
1618                        String pk = metaData.columnNameOf(i);
1619                        Object[] data = map.get(pk);
1620                        checkArgument(data!=null,"PRIMARY KEY %s NOT DEFINED",pk);
1621                        String linkedTableName = (String)data[0];
1622                        int linkedColumnId = ((Integer)data[1]).intValue();
1623
1624                        if(left.tableName().equals(linkedTableName)){
1625                                primaryValues[i] = left.getValueChecked(linkedColumnId);
1626                        }else if (right.tableName().equals(linkedTableName)){
1627                                primaryValues[i] = right.getValueChecked(linkedColumnId);
1628                        } else {
1629                                throw new IllegalArgumentException(String.format("%s NOT linked table  %s", linkedTableName,metaData.tablename));
1630                        }
1631                }
1632                return createBean(primaryValues);
1633    }
1634
1635    /**
1636     * add junction between L and R if junction not exists
1637     * @param <L>
1638     * @param <R>
1639     * @param left
1640     * @param right
1641     */    
1642    public <L extends BaseBean,R extends BaseBean> 
1643    void addJunction(L left,R right){
1644        if(hasNullPk(left) || hasNullPk(right)){
1645            return ;
1646        }
1647        B junction = makeJunctionBean(left,right);
1648        if(!existsByPrimaryKey(junction)){
1649            save(junction);
1650        }
1651    }
1652
1653    /**
1654     * remove junction between L and R if junction not exists
1655     * @param <L>
1656     * @param <R>
1657     * @param left
1658     * @param right
1659     */    
1660    public <L extends BaseBean,R extends BaseBean> 
1661    int deleteJunction(L left,R right){
1662        if(hasNullPk(left) || hasNullPk(right)){
1663            return 0;
1664        }
1665        B junction = makeJunctionBean(left,right);
1666        return delete(junction);
1667    }
1668    
1669    @SuppressWarnings("unchecked")
1670    public <L extends BaseBean,R extends BaseBean>
1671    void addJunction(L left,R... rights){
1672        if(null != rights){
1673                addJunction(left, Arrays.asList(rights));
1674        }
1675    }
1676
1677    public <L extends BaseBean,R extends BaseBean> 
1678    void addJunction(L left,Collection<R> rights){
1679        if(null != rights){
1680            for(R linked:rights){
1681                addJunction(left,linked);
1682            }
1683        }
1684    }
1685    @SuppressWarnings("unchecked")
1686    public <L extends BaseBean,R extends BaseBean>
1687    int deleteJunction(L left,R... rights){
1688        if(null != rights){
1689                return deleteJunction(left, Arrays.asList(rights));
1690        }
1691        return 0;
1692    }
1693    public <L extends BaseBean,R extends BaseBean>
1694    int deleteJunction(L left,Collection<R> rights){
1695        int count = 0;
1696        if(null != rights){
1697            for(R right:rights){
1698                count += deleteJunction(left,right);
1699            }
1700        }
1701        return count;
1702    }
1703
1704    //28-2
1705    /** decode a resultset and call action
1706     * @param rs the resultset to decode
1707     * @param fieldList table of the field's associated constants
1708     * @param startRow the start row to be used (first row = 1, last row = -1)
1709     * @param numRows the number of rows to be retrieved (all rows = a negative number)
1710     * @param action interface obj for do something
1711     * @return the count dealt by action  
1712     * @throws IllegalArgumentException
1713     */
1714    protected int actionOnResultSet(ResultSet rs, int[] fieldList, int startRow, int numRows, Action<B> action) throws DaoException{
1715        try{
1716            int count = 0;
1717            if(0!=numRows){
1718                checkArgument(startRow>=1,"invalid argument:startRow (must >=1)");
1719                checkArgument(null !=action && null != rs,"invalid argument:action OR rs (must not be null)");
1720                        rs.absolute(startRow - 1);
1721                if (fieldList == null) {
1722                    if(numRows<0){
1723                        for(;rs.next();++count){
1724                            action.call(decodeRow(rs));
1725                        }
1726                    }else{
1727                        for(;rs.next() && count<numRows;++count){
1728                            action.call(decodeRow(rs));
1729                        }
1730                    }
1731                }else {
1732                    if(numRows<0){
1733                        for(;rs.next();++count){
1734                            action.call(decodeRow(rs, fieldList));
1735                        }
1736                    }else{
1737                        for(;rs.next() && count<numRows;++count){
1738                            action.call(decodeRow(rs, fieldList));
1739                        }
1740                    }
1741                }
1742            }
1743            return count;
1744        }catch(DaoException e){
1745            throw e;
1746        }catch(SQLException e){
1747            throw new DataAccessException(e);
1748        }
1749    }
1750
1751        @SuppressWarnings("unchecked")
1752        protected <T> T getColumnValue(ResultSet resultSet, int columnId) throws SQLException {
1753                return (T) Manager.getObject(resultSet,columnId + 1, metaData.columnTypeOf(columnId));
1754        }
1755        protected void setColumnValue(B bean,int columnId, Object value ){
1756                if(value instanceof byte[] && ByteBuffer.class.equals(metaData.columnTypeOf(columnId))){
1757                        value = ByteBuffer.wrap((byte[])value);
1758                }
1759                bean.setValue(columnId, value);
1760        }
1761    //29
1762    /**    
1763     * Transforms a ResultSet iterating on a B bean.
1764     *
1765     * @param rs the ResultSet to be transformed
1766     * @return bean resulting B bean
1767     */
1768    private B decodeRow(ResultSet rs) throws DaoException
1769    {
1770        B   bean = createBean();
1771        try
1772        {
1773                for(int i=0; i<metaData.columnNames.size(); ++i){
1774                        setColumnValue(bean, i, getColumnValue(rs,i));
1775                }
1776        }
1777        catch(SQLException e)
1778        {
1779            throw new DataAccessException(e);
1780        }
1781        bean.setNew(false);
1782        bean.resetIsModified();
1783
1784        return bean;
1785    }
1786
1787    //30
1788    /**
1789     * Transforms a ResultSet iterating on a B bean according to a list of fields.
1790     *
1791     * @param rs the ResultSet to be transformed
1792     * @param fieldList table of the field's associated constants
1793     * @return bean resulting B bean
1794     */
1795    private B decodeRow(ResultSet rs, int[] fieldList) throws DaoException
1796    {
1797        fieldList = MoreObjects.firstNonNull(fieldList, metaData.defaultColumnIdList);
1798        B bean = createBean();
1799        try
1800        {
1801            for(int i = 0; i < fieldList.length; i++)
1802            {
1803                if(fieldList[i]>=0 && fieldList[i]<metaData.columnNames.size()){
1804                        int columnId = fieldList[i];
1805                        setColumnValue(bean, columnId, getColumnValue(rs, columnId) );
1806                }else{
1807                        throw new DaoException("Unknown field id " + fieldList[i]);
1808                }
1809            }
1810        }
1811        catch(SQLException e)
1812        {
1813            throw new DataAccessException(e);
1814        }
1815        bean.setNew(false);
1816        bean.resetIsModified();
1817
1818        return bean;
1819    }
1820    //////////////////////////////////////
1821    // PREPARED STATEMENT LOADER
1822    //////////////////////////////////////
1823
1824    //34-1
1825    /**
1826     * Loads all the elements using a prepared statement specifying a list of fields to be retrieved,
1827     * and specifying the start row and the number of rows.
1828     *
1829     * @param ps the PreparedStatement to be used
1830     * @param startRow the start row to be used (first row = 1, last row = -1)
1831     * @param numRows the number of rows to be retrieved (all rows = a negative number)
1832     * @param fieldList table of the field's associated constants
1833     * @return an array of B
1834     */
1835    protected List<B> loadByPreparedStatementAsList(PreparedStatement ps, int[] fieldList, int startRow, int numRows) throws DaoException
1836    {
1837        ListAction action = new ListAction();
1838        loadByPreparedStatement(ps,fieldList,startRow,numRows,action);
1839        return action.getList();
1840    }
1841    //34-2
1842    /**
1843     * Loads each element using a prepared statement specifying a list of fields to be retrieved,
1844     * and specifying the start row and the number of rows 
1845     * and dealt by action.
1846     *
1847     * @param ps the PreparedStatement to be used
1848     * @param startRow the start row to be used (first row = 1, last row = -1)
1849     * @param numRows the number of rows to be retrieved (all rows = a negative number)
1850     * @param fieldList table of the field's associated constants
1851     * @param action Action object for do something(not null)
1852     * @return the count dealt by action
1853     */     
1854    private int loadByPreparedStatement(PreparedStatement ps, int[] fieldList, int startRow, int numRows,Action<B> action) throws DaoException
1855    {
1856        ResultSet rs =  null;
1857        try { 
1858            ps.setFetchSize(100);
1859            rs = ps.executeQuery();
1860            return this.actionOnResultSet(rs, fieldList, startRow, numRows, action);
1861        } catch (DaoException e) {
1862            throw e;
1863        } catch (SQLException e) {
1864            throw new DataAccessException(e);
1865        } finally {
1866            this.getManager().close(rs);
1867        }
1868    }
1869    //_____________________________________________________________________
1870    //
1871    // LISTENER
1872    //_____________________________________________________________________    
1873
1874    /** foreign key listener for DEELTE RULE */
1875    protected class  DeleteRuleListener<F extends BaseBean> extends BaseForeignKeyListener<F,B>{
1876        protected final String fkName;
1877        private final ForeignKeyRule deleteRule;
1878        private final int[] foreignKeyIds;
1879        DeleteRuleListener(String fkName) {
1880                checkArgument(metaData.foreignKeys.containsKey(fkName),"INVALID foreign key name %s",fkName);
1881                this.fkName = fkName;
1882                this.deleteRule = metaData.foreignKeys.get(fkName).deleteRule;
1883                this.foreignKeyIds = metaData.foreignKeyIdArrayOf(fkName);
1884        }
1885        @Override
1886        protected List<B> getImportedBeans(F bean) {
1887                return getListenerContainer().isEmpty() 
1888                                ? Collections.<B>emptyList()
1889                                : loadByForeignKeyAsList(bean,fkName,1,-1);
1890        }
1891                @SuppressWarnings("unchecked")
1892                @Override
1893                protected void onRemove(List<B> affectedBeans) throws DaoException {
1894                        switch (deleteRule) {
1895                        case SET_NULL:
1896                                for(B bean : affectedBeans){
1897                                        BaseRow updated = ((BaseRow)bean).clone();
1898                                        for(int i=0; i<foreignKeyIds.length; ++i){
1899                                                updated.setValue(foreignKeyIds[i], null);
1900                                        }
1901                                        fire(Event.UPDATE, (B) updated);
1902                                }
1903                                break;
1904                        case SET_DEFAULT:
1905                                B tmpl = createBean();
1906                                for(B bean : affectedBeans){
1907                                        BaseRow updated = ((BaseRow)bean).clone();
1908                                        for(int i=0; i<foreignKeyIds.length; ++i){
1909                                                updated.setValue(foreignKeyIds[i], tmpl.getValue(foreignKeyIds[i]));
1910                                        }
1911                                        fire(Event.UPDATE, (B) updated);
1912                                }
1913                                break;
1914                        default:
1915                                if(!deleteRule.isNoAction()){
1916                                        Event event = Event.valueOf(deleteRule.eventOfDeleteRule);
1917                                        for(B bean : affectedBeans){
1918                                                fire(event, bean);
1919                                        }
1920                                }
1921                                break;
1922                        }
1923                }
1924                
1925                /**
1926                 * fire current event by  {@link TableListener}
1927                 * @param event
1928                 * @param bean
1929                 * @throws RuntimeDaoException
1930                 */
1931                private void fire(Event event,B bean)throws RuntimeDaoException {
1932
1933                        ListenerContainer<B> container = getListenerContainer();
1934                        switch(event){
1935                        case UPDATE:
1936                                container.beforeUpdate(bean);
1937                                container.afterUpdate(bean);
1938                                container.done();
1939                                break;
1940                        case DELETE:
1941                                container.beforeDelete(bean);
1942                                container.afterDelete(bean);
1943                                container.done();
1944                                break;
1945                        case INSERT:
1946                                // DO NOTHING
1947                                // container.beforeInsert(bean);
1948                                // container.afterInsert(bean);
1949                                // container.done();
1950                        default:
1951                                break;
1952                        }
1953                }
1954
1955                @Override
1956                public String toString() {
1957                        StringBuilder builder = new StringBuilder();
1958                        builder.append("DeleteRuleListener [fkName=");
1959                        builder.append(fkName);
1960                        builder.append(", deleteRule=");
1961                        builder.append(deleteRule);
1962                        builder.append(", foreignKeyIds=");
1963                        builder.append(metaData.columnNamesOf(foreignKeyIds));
1964                        builder.append("]");
1965                        return builder.toString();
1966                }       
1967    }
1968
1969    //35
1970
1971    @Override
1972    public void registerListener(TableListener<B> listener)
1973    {
1974        this.getListenerContainer().add(listener);
1975    }
1976
1977    //36
1978
1979    @Override
1980    public void unregisterListener(TableListener<B> listener)
1981    {
1982        this.getListenerContainer().remove(listener);
1983    }
1984    
1985    //37-2
1986    /**
1987     * bind foreign key listener to foreign table: <br>
1988     * DELETE RULE <br>
1989     */
1990    public void bindForeignKeyListenerForDeleteRule(){
1991        for(Entry<String, TableListener<BaseBean>> entry : getForeignKeyDeleteListeners().entrySet()){
1992                TableManager<BaseBean> manager = managerOf(metaData.foreignKeys.get(entry.getKey()).foreignTable);
1993                manager.registerListener(entry.getValue());
1994        }
1995    }
1996    
1997  //37-3
1998    /**
1999     * unbind foreign key listener from all of foreign tables <br>
2000     * @see #bindForeignKeyListenerForDeleteRule()
2001     */
2002    public void unbindForeignKeyListenerForDeleteRule(){
2003        for(Entry<String, TableListener<BaseBean>> entry : getForeignKeyDeleteListeners().entrySet()){
2004                TableManager<BaseBean> manager = managerOf(metaData.foreignKeys.get(entry.getKey()).foreignTable);
2005                manager.unregisterListener(entry.getValue());
2006        }
2007    }
2008    //_____________________________________________________________________
2009    //
2010    // UTILS
2011    //_____________________________________________________________________
2012    //40
2013    /**
2014     * Retrieves the manager object used to get connections.
2015     *
2016     * @return the manager used
2017     */
2018    protected Manager getManager()
2019    {
2020        return Manager.getInstance();
2021    }
2022
2023    //41
2024    /**
2025     * Frees the connection.
2026     *
2027     * @param c the connection to release
2028     */
2029    protected void freeConnection(Connection c)
2030    {
2031        // back to pool
2032        this.getManager().releaseConnection(c);
2033    }
2034
2035    //42
2036    /**
2037     * Gets the connection.
2038     */
2039    protected Connection getConnection() throws DaoException
2040    {
2041        try
2042        {
2043            return this.getManager().getConnection();
2044        }
2045        catch(SQLException e)
2046        {
2047            throw new DataAccessException(e);
2048        }
2049    }
2050    
2051    private int loadBySqlForAction(String sql, Object[] argList, int[] fieldList,int startRow, int numRows,Action<B> action){
2052        PreparedStatement ps = null;
2053        Connection connection = null;
2054        try {
2055            connection = this.getConnection();
2056            if(debug){
2057                log("loadBySqlForAction:" + sql );
2058            }
2059            ps = connection.prepareStatement(sql,
2060                    ResultSet.TYPE_FORWARD_ONLY,
2061                    ResultSet.CONCUR_READ_ONLY);
2062            Manager.fillPrepareStatement(ps, argList);
2063            return this.loadByPreparedStatement(ps, fieldList, startRow, numRows, action);
2064        } catch (DaoException e) {
2065            throw new RuntimeDaoException(e);
2066        }catch (SQLException e) {
2067            throw new RuntimeDaoException(new DataAccessException(e));
2068        } finally {
2069            this.getManager().close(ps);
2070            this.freeConnection(connection);
2071        }
2072    }
2073
2074    @Override
2075        public List<BaseBean> runSqlAsList(String sql, Object... argList) throws RuntimeDaoException{
2076        return getManager().runSqlAsList(sql, argList);
2077    }
2078    
2079    @Override
2080        public List<Map<String, Object>> runSqlForMap(Map<String,Class<?>> targetTypes, String sql,Object... argList) throws RuntimeDaoException{
2081        return getManager().runSqlForMap(targetTypes, sql, argList);
2082    }
2083    
2084    @Override
2085        public <T>List<T> runSqlAsList(Class<T> targetType, String sql,Object... argList) throws RuntimeDaoException{
2086        return getManager().runSqlAsList(targetType, sql, argList);
2087    }
2088    
2089    @Override
2090        public <T> T runSqlForValue(Class<T> targetType,String sql, Object... argList) throws RuntimeDaoException{
2091        return getManager().runSqlForValue(targetType, sql, argList);
2092    }
2093    
2094    @Override
2095    public <T>T runAsTransaction(Callable<T> fun) {
2096       return getManager().runAsTransaction(fun);
2097    }
2098    
2099    @Override
2100    public void runAsTransaction(Runnable fun) {
2101        getManager().runAsTransaction(fun);
2102    }    
2103    protected class DeleteBeanAction implements Action<B>{
2104        private final AtomicInteger count=new AtomicInteger(0);
2105        public DeleteBeanAction() {
2106                }
2107                @Override
2108        public void call(B bean){
2109                delete(bean);
2110                count.incrementAndGet();
2111        }
2112        public int getCount(){
2113            return count.get();
2114        }
2115    }
2116    
2117    //45 
2118
2119    /**
2120     * @param <T> PK type
2121     * @param type
2122     * @param beans
2123     * @return return a primary key list from B array
2124     * @see #toPrimaryKeyList(Class,Collection)
2125     */
2126    @SuppressWarnings("unchecked")
2127        protected <T> List<T> toPrimaryKeyList(Class<T>type,B... beans){        
2128        if(null == beans || beans.length ==0){
2129            return Collections.emptyList();
2130        }
2131        return toPrimaryKeyList(type,Arrays.asList(beans));
2132    }
2133    
2134    private class ColumnTransformer<T> implements Function<B, T>{
2135        private int columnId;
2136                public ColumnTransformer(int columnId) {
2137                        checkArgument(columnId >=0 && columnId < metaData.columnCount,"INVALID column id %s",columnId);
2138                        this.columnId = columnId;
2139                }
2140                @Override
2141                public T apply(B input) {
2142                        return input == null ? null : input.<T>getValue(columnId);
2143                }       
2144    }
2145    /**
2146         * listener event:<br>
2147         * {@code INSERT} insert a bean<br>
2148         * {@code UPDATE} update a bean<br>
2149         * {@code DELETE} delete a bean<br>
2150         * @author guyadong
2151         *
2152         */
2153        public static enum Event{        
2154            /** insert a bean */INSERT,
2155            /** update a bean */UPDATE,
2156            /** delete a bean */DELETE
2157        }
2158
2159        //46 
2160    /**
2161     * return a primary key list from B collection<br>
2162     * throw {@link UnsupportedOperationException} if there is more than a primary key
2163     * @param <T> PK type
2164     * @param type PK type
2165     * @param beans input beans
2166     */
2167    protected <T> List<T> toPrimaryKeyList(Class<T>type,Collection<B> beans){        
2168        if(metaData.primaryKeyNames.length != 1){
2169                throw new UnsupportedOperationException();
2170        }
2171        if(null == beans){
2172            return Collections.emptyList();
2173        }
2174        checkArgument(metaData.columnTypeOf(metaData.primaryKeyIds[0]) != type,"INVALID primary key type: " + type);
2175        return ImmutableList.copyOf(Iterables.transform(beans, new ColumnTransformer<T>(metaData.primaryKeyIds[0])));
2176    }
2177    private Object[] toPkValues(B bean,int[] selfFkIds){
2178        checkArgument(selfFkIds.length == metaData.primaryKeyNames.length,"MISMATCH SIZE of primary keys");
2179        return bean.asValueArray(selfFkIds);
2180    }
2181    
2182    protected List<B> listOfSelfRef(String fkName,Object... primaryKeys){
2183        List<B> list = new ArrayList<>();
2184        int[] selfFks = metaData.foreignKeyIdArrayOf(fkName); 
2185        for(B ref = loadByPrimaryKey(primaryKeys)
2186                ; null != ref
2187                ; ref = loadByPrimaryKey(toPkValues(ref,selfFks))){
2188            list.add(ref);
2189            Object[] refPk = ref.primaryValues();
2190            Object[] refSelf = toPkValues(ref,selfFks);
2191            
2192            if(Arrays.equals(refPk,refSelf)
2193                        || (list.size() > 1 && Arrays.equals(refPk,primaryKeys))){
2194                // cycle reference
2195                break;
2196            }
2197        }
2198        Collections.reverse(list);
2199        return list;
2200    }
2201    protected int levelOfSelfRef(String fkName,Object... primaryKeys){
2202        int count = 0 ;
2203        int[] selfFks = metaData.foreignKeyIdArrayOf(fkName); 
2204        for(B ref = loadByPrimaryKey(primaryKeys)
2205           ; null != ref
2206           ; ++count,ref = loadByPrimaryKey(toPkValues(ref,selfFks))){
2207            Object[] refPk = ref.primaryValues();
2208            Object[] refSelf = toPkValues(ref,selfFks);
2209            if(    (Arrays.equals(refPk,refSelf))
2210                || (count > 0 && Arrays.equals(refPk,primaryKeys))){
2211                // cycle reference
2212                return -1;
2213            }
2214        }
2215        return count;
2216    }
2217    protected B topOfSelfRef(String fkName,Object... primaryKeys){
2218        checkArgument(!hasNull(primaryKeys),"primaryKeys has null element");
2219        int[] selfFks = metaData.foreignKeyIdArrayOf(fkName); 
2220
2221        B ref = loadByPrimaryKey(primaryKeys);
2222        int count = 0 ;
2223        Object[] refSelf = toPkValues(ref,selfFks);
2224
2225        for(;null != ref && !hasNull(refSelf);){
2226            Object[] refPk = ref.primaryValues();
2227            refSelf = toPkValues(ref,selfFks);
2228
2229                if(   (Arrays.equals(refPk,refSelf))
2230                || (++count > 1 && Arrays.equals(refPk,primaryKeys))){
2231                // cycle reference
2232                throw new IllegalStateException("cycle on field: " + "parent");
2233            }
2234                checkState(Arrays.equals(refPk,refSelf)
2235                || (++count > 1 && Arrays.equals(refPk,primaryKeys)));
2236            ref = loadByPrimaryKeyChecked(refSelf);
2237        }
2238        return ref;
2239    }
2240    protected LinkedHashSet<B> doListOfChild(B bean, LinkedHashSet<B> set,String fkName){
2241        if(existsByPrimaryKey(bean)){
2242            checkState(!set.contains(bean),"cycle on foreign key: " + fkName);
2243            set.add(bean);
2244            List<B> childs = loadByForeignKeyAsList(bean, fkName, 1, -1);
2245            for(B c:childs){
2246                doListOfChild(c,set,fkName);
2247            }
2248        }
2249        return set;
2250    }
2251        @Override
2252        public int hashCode() {
2253                final int prime = 31;
2254                int result = 1;
2255                result = prime * result + ((metaData == null) ? 0 : metaData.hashCode());
2256                return result;
2257        }
2258
2259        @Override
2260        public boolean equals(Object obj) {
2261                if (this == obj) {
2262                        return true;
2263                }
2264                if (obj == null) {
2265                        return false;
2266                }
2267                if (getClass() != obj.getClass()) {
2268                        return false;
2269                }
2270                BaseTableManager<?> other = (BaseTableManager<?>) obj;
2271                if (metaData == null) {
2272                        if (other.metaData != null) {
2273                                return false;
2274                        }
2275                } else if (!metaData.equals(other.metaData)) {
2276                        return false;
2277                }
2278                return true;
2279        }
2280
2281        @Override
2282        public String toString() {
2283                StringBuilder builder = new StringBuilder();
2284                builder.append(getClass().getSimpleName() + " [metaData=");
2285                builder.append(metaData);
2286                builder.append("]");
2287                return builder.toString();
2288        }
2289
2290        /**
2291         * set debug flag that determine if output log message,default : false
2292         * @param debug flag for debug message output
2293         */
2294        public static void setDebug(boolean debug) {
2295                BaseTableManager.debug = debug;
2296        }
2297    private static final ImmutableMap<String, TableManager<? extends BaseBean>> 
2298        tableManagerInstances = loadTableManager(); 
2299    private static final  ImmutableMap<Class<?>, TableManager<? extends BaseBean>> 
2300        tableManagerTypeMaps = asTypeMap(tableManagerInstances);
2301    private static final  ImmutableMap<Class<?>, TableManager<? extends BaseBean>> 
2302        tableManagerBeanTypeMaps = asBeanTypeMap(tableManagerInstances);
2303        /**
2304         * SPI(Service Provider Interface)机制加载 {@link TableManager}所有实例
2305         * @return 表名和 {@link TableManager}实例的映射对象 
2306         */
2307        @SuppressWarnings({ "rawtypes" })
2308        private static ImmutableMap<String, TableManager<? extends BaseBean>> loadTableManager() {          
2309                ServiceLoader<TableManager> providers = ServiceLoader.load(TableManager.class);
2310                Iterator<TableManager> itor = providers.iterator();
2311                TableManager<?> node;
2312                ImmutableMap.Builder<String, TableManager<? extends BaseBean>> builder = ImmutableMap.builder();
2313                while(itor.hasNext()){
2314                        node = itor.next();
2315                        if(node instanceof BaseTableManager){
2316                                BaseTableManager<?> manager = (BaseTableManager<?>)node;
2317                                builder.put(manager.metaData.tablename, manager);
2318                        }
2319                }
2320                return builder.build();
2321        }
2322        private static final ImmutableMap<Class<?>, TableManager<? extends BaseBean>> asTypeMap(Map<String, TableManager<? extends BaseBean>>input){
2323                return Maps.uniqueIndex(input.values(), new Function<TableManager<? extends BaseBean>,Class<?>>(){
2324
2325                        @Override
2326                        public Class<?> apply(TableManager<? extends BaseBean> input) {
2327                                return input.getClass();
2328                        }});
2329        }
2330        private static final ImmutableMap<Class<?>, TableManager<? extends BaseBean>> asBeanTypeMap(Map<String, TableManager<? extends BaseBean>>input){
2331                return Maps.uniqueIndex(input.values(), new Function<TableManager<? extends BaseBean>,Class<?>>(){
2332
2333                        @Override
2334                        public Class<?> apply(TableManager<? extends BaseBean> input) {
2335                                return ((BaseTableManager<?>)input).metaData.beanType;
2336                        }});
2337        }
2338        public static ImmutableMap<String, TableManager<? extends BaseBean>> getTableManagers() {
2339                return tableManagerInstances;
2340        }
2341
2342        @SuppressWarnings("unchecked")
2343        public static final <M extends TableManager<?>>M 
2344        getTableManager(Class<M>managerType) {
2345                TableManager<? extends BaseBean> manager = tableManagerTypeMaps.get(managerType);
2346                return checkNotNull((M) manager,"INVALID manager type %s",managerType);
2347        }
2348        @SuppressWarnings("unchecked")
2349        public static final <T extends BaseBean,M extends TableManager<T>>M 
2350        getTableManagerByBeanType(Class<T>beanType) {
2351                TableManager<? extends BaseBean> manager = tableManagerBeanTypeMaps.get(beanType);
2352                return checkNotNull((M) manager,"INVALID bean type %s",beanType);
2353        }
2354        @SuppressWarnings("unchecked")
2355        public static final <M extends TableManager<?>>M 
2356        getTableManager(String tablename) {
2357                TableManager<? extends BaseBean> manager = tableManagerInstances.get(tablename);
2358                return checkNotNull((M) manager,"INVALID tablename %s",tablename);
2359        }
2360        
2361    private static final  Map<Class<?>, TableManager<? extends BaseBean>> cacheManagers = Maps.newHashMap();
2362    private static final  Map<Class<?>, TableManager<? extends BaseBean>> cacheBeanTypeManagers = Maps.newHashMap();
2363    private static final  Map<String, TableManager<? extends BaseBean>> cacheNameManagers = Maps.newHashMap();
2364        /**
2365         * 注册cache manager
2366         * @param cacheManager
2367         */
2368        public synchronized static void registerCacheManager(ICacheManager cacheManager) {              
2369                if(cacheManager instanceof BaseTableManager<?>){
2370                        BaseTableManager<?> manager = (BaseTableManager<?>)cacheManager;
2371                        Class<? extends ICacheManager> clazz = cacheManager.getClass();
2372                        cacheManagers.put(clazz.getSuperclass(), manager);
2373                        cacheNameManagers.put(manager.metaData.tablename, manager);
2374                        cacheBeanTypeManagers.put(manager.metaData.beanType, manager);
2375                }
2376        }
2377
2378        public static Map<Class<?>, TableManager<? extends BaseBean>> getCacheManagers() {
2379                return Collections.unmodifiableMap(cacheManagers);
2380        }
2381
2382        /**
2383         * 根据目标类型返回对应的 {@link TableManager}实例
2384         * @param targetType 目标类型
2385         * @return {@link TableManager}实例
2386         * @throws NoSuchElementException 找不到时抛出异常
2387         */
2388        @SuppressWarnings("unchecked")
2389        public static final <M extends TableManager<? extends BaseBean>>M 
2390        getCacheManager(final Class<M>targetType) throws NoSuchElementException {
2391                checkArgument(targetType != null,"targetType is null");
2392                TableManager<? extends BaseBean> manager;
2393                if(null != (manager = cacheManagers.get(targetType))){
2394                        return (M) manager;
2395                }
2396                return (M) Iterables.find(cacheManagers.values(), new Predicate<TableManager<?>>() {
2397
2398                        @Override
2399                        public boolean apply(TableManager<?> input) {
2400                                return targetType.isInstance(input);
2401                        }
2402                });
2403        }
2404        @SuppressWarnings("unchecked")
2405        public static final <M extends TableManager<?>> M 
2406        getCacheManagerByBeanType(Class<?> beanType)  {
2407                TableManager<? extends BaseBean> manager = cacheBeanTypeManagers.get(beanType);
2408                return (M) checkNotNull(manager,"INVALID bean type %s",beanType);
2409        }
2410        @SuppressWarnings("unchecked")
2411        public static final <M extends TableManager<?>> M 
2412        getCacheManager(String tablename)  {
2413                TableManager<? extends BaseBean> manager = cacheNameManagers.get(tablename);
2414                return (M) checkNotNull(manager,"INVALID table name %s",tablename);
2415        }
2416
2417        public static <T extends BaseBean,M extends TableManager<? extends BaseBean>>M 
2418        instanceOf(Class<M>targetType) {
2419            try {
2420                return getCacheManager(targetType);
2421            } catch (Exception e) {
2422                return getTableManager(targetType);
2423            } 
2424        }
2425
2426        public static <M extends TableManager<? extends BaseBean>>M managerOf(String tablename) {
2427            try {
2428                return getCacheManager(tablename);
2429            } catch (Exception e) {
2430                return getTableManager(tablename);
2431            } 
2432        }
2433
2434        public static <T extends BaseBean,M extends TableManager<T>>M managerOf(Class<T> beanType) {
2435            try {
2436                return getCacheManagerByBeanType(beanType);
2437            } catch (Exception e) {
2438                return getTableManagerByBeanType(beanType);
2439            } 
2440        }
2441}