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