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