/*
 * This software is distributed under following license based on modified BSD
 * style license.
 * ----------------------------------------------------------------------
 * 
 * Copyright 2009 The Nimbus2 Project. All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer. 
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE NIMBUS PROJECT ``AS IS'' AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
 * NO EVENT SHALL THE NIMBUS PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * 
 * The views and conclusions contained in the software and documentation are
 * those of the authors and should not be interpreted as representing official
 * policies, either expressed or implied, of the Nimbus2 Project.
 */
package jp.ossc.nimbus.service.context;

import java.io.Serializable;
import java.lang.reflect.Array;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import jp.ossc.nimbus.beans.IndexedProperty;
import jp.ossc.nimbus.beans.PropertyAccess;
import jp.ossc.nimbus.beans.Property;
import jp.ossc.nimbus.beans.dataset.PropertySchema;
import jp.ossc.nimbus.beans.dataset.Record;
import jp.ossc.nimbus.beans.dataset.RecordList;
import jp.ossc.nimbus.core.ServiceBase;
import jp.ossc.nimbus.core.ServiceName;
import jp.ossc.nimbus.core.ServiceManagerFactory;
import jp.ossc.nimbus.service.sql.ConnectionFactory;
import jp.ossc.nimbus.service.sql.PersistentManager;
import jp.ossc.nimbus.service.queue.AsynchContext;
import jp.ossc.nimbus.service.queue.DefaultQueueService;
import jp.ossc.nimbus.service.queue.QueueHandler;
import jp.ossc.nimbus.service.queue.QueueHandlerContainerService;

/**
 * データベース共有コンテキストストア。<p>
 *
 * @author M.Takata
 */
public class DatabaseSharedContextStoreService<K> extends ServiceBase
 implements SharedContextStore<K>, DatabaseSharedContextStoreServiceMBean{
    
    private static final long serialVersionUID = 5260610052471948594L;
    
    private ServiceName connectionFactoryServiceName;
    private ConnectionFactory connectionFactory;
    
    private ServiceName persistentManagerServiceName;
    private PersistentManager persistentManager;
    
    private List<DatabaseMapping<K>> databaseMappings;
    
    public void setConnectionFactoryServiceName(ServiceName name){
        connectionFactoryServiceName = name;
    }
    public ServiceName getConnectionFactoryServiceName(){
        return connectionFactoryServiceName;
    }
    
    public void setPersistentManagerServiceName(ServiceName name){
        persistentManagerServiceName = name;
    }
    public ServiceName getPersistentManagerServiceName(){
        return persistentManagerServiceName;
    }
    
    public void setConnectionFactory(ConnectionFactory factory){
        connectionFactory = factory;
    }
    
    public void setPersistentManager(PersistentManager pm){
        persistentManager = pm;
    }
    
    public void addDatabaseMapping(DatabaseMapping<K> mapping){
        databaseMappings.add(mapping);
    }
    
    public void createService() throws Exception{
        databaseMappings = new ArrayList<DatabaseMapping<K>>();
    }
    
    public void startService() throws Exception{
        if(connectionFactoryServiceName != null){
            connectionFactory = ServiceManagerFactory.getServiceObject(connectionFactoryServiceName);
        }
        if(connectionFactory == null){
            throw new IllegalArgumentException("ConnectionFactory is null.");
        }
        if(persistentManagerServiceName != null){
            persistentManager = ServiceManagerFactory.getServiceObject(persistentManagerServiceName);
        }
        if(persistentManager == null){
            throw new IllegalArgumentException("PersistentManager is null.");
        }
    }
    
    public synchronized void clear() throws Exception{
        for(int i = 0; i < databaseMappings.size(); i++){
            databaseMappings.get(i).clear(connectionFactory, persistentManager);
        }
    }
    
    public synchronized void save(SharedContext<K> context) throws Exception{
        for(int i = 0; i < databaseMappings.size(); i++){
            databaseMappings.get(i).save(context, connectionFactory, persistentManager);
        }
    }
    
    public synchronized void load(SharedContext<K> context) throws Exception{
        for(int i = 0; i < databaseMappings.size(); i++){
            databaseMappings.get(i).load(context, connectionFactory, persistentManager);
        }
    }
    
    /**
     * データベースマッピング。<p>
     * 
     * @author M.Takata
     */
    public static class DatabaseMapping<K> implements Serializable{
        
        private static final long serialVersionUID = 4632610348315541284L;
        
        protected String selectQuery;
        protected String parallelKeySelectQuery;
        protected String parallelSelectQuery;
        protected String insertQuery;
        protected String insertKeySelectQuery;
        protected String deleteQuery;
        
        protected String keyLoadPropertyName;
        protected String keySavePropertyName;
        protected Class<?> keyClass;
        protected Map<String, String> keyLoadPropertyMappings;
        protected Map<String, String> keySavePropertyMappings;
        
        protected Record databaseRecord;
        protected RecordList databaseRecordList;
        protected Map<String, Map<String, List<String>>> loadPropertyMappings;
        protected Map<String, Map<String, List<String>>> savePropertyMappings;
        protected List<DatabaseSubMapping> subMappings;
        protected int fetchSize;
        protected int batchPersistCount;
        protected boolean isBatchCommitOnPersist = true;
        protected boolean isUseSubCursor = true;
        protected boolean isUseSubConnection = false;
        protected int parallelSize = 0;
        protected Class<?> valueClass;
        protected PropertyAccess propertyAccess;
        protected boolean isUniqueKey = true;
        protected boolean isSort;
        protected String[] sortPropertyNames;
        protected boolean[] isAsc;
        
        /**
         * キーがユニークキーかどうかを設定する。<p>
         * デフォルトはtrueで、キーはユニーク。<br>
         *
         * @param isUnique キーがユニークキーの場合は、true
         */
        public void setUniqueKey(boolean isUnique){
            isUniqueKey = isUnique;
        }
        
        /**
         * 読み込みに使用する検索クエリを設定する。<p>
         *
         * @param query クエリ
         */
        public void setSelectQuery(String query){
            selectQuery = query;
        }
        
        /**
         * キー単位での並列読み込みの際にキーを検索するクエリを設定する。<p>
         *
         * @param query クエリ
         */
        public void setParallelKeySelectQuery(String query){
            parallelKeySelectQuery = query;
        }
        
        /**
         * キー単位での並列読み込みにの際にキーが条件句となる検索クエリを設定する。<p>
         *
         * @param query クエリ
         */
        public void setParallelSelectQuery(String query){
            parallelSelectQuery = query;
        }
        
        /**
         * キー単位での並列読み込みの際の並列度を設定する。<p>
         *
         * @param size 並列度
         */
        public void setParallelSize(int size){
            parallelSize = size;
        }
        
        /**
         * 保存の際に使用する挿入クエリを設定する。<p>
         *
         * @param query クエリ
         */
        public void setInsertQuery(String query){
            insertQuery = query;
        }
        
        /**
         * 保存の際に保存するキーを特定する検索クエリを設定する。<p>
         *
         * @param query クエリ
         */
        public void setInsertKeySelectQuery(String query){
            insertKeySelectQuery = query;
        }
        
        /**
         * 削除の際に使用する削除クエリを設定する。<p>
         *
         * @param query クエリ
         */
        public void setDeleteQuery(String query){
            deleteQuery = query;
        }
        
        /**
         * PersistentManagerに渡すRecordオブジェクトを設定する。<p>
         * Beanに直接マッピングする場合には、設定する必要はない。<br>
         * 
         * @param record 
         */
        public void setDatabaseRecord(Record record){
            databaseRecord = record;
        }
        
        /**
         * PersistentManagerに渡すRecordを生成するRecordListオブジェクトを設定する。<p>
         * Beanに直接マッピングする場合には、設定する必要はない。<br>
         * 
         * @param list 
         */
        public void setDatabaseRecordList(RecordList list){
            databaseRecordList = list;
            databaseRecord = list == null ? null : list.createRecord();
        }
        
        /**
         * 読み込んだRecordからキーとして取りだすプロパティ名を設定する。<p>
         *
         * @param name プロパティ名
         */
        public void setKeyLoadPropertyName(String name){
            keyLoadPropertyName = name;
        }
        
        /**
         * キーをRecordのプロパティへ設定するためのプロパティ名を設定する。<p>
         *
         * @param name プロパティ名
         */
        public void setKeySavePropertyName(String name){
            keySavePropertyName = name;
        }
        
        /**
         * キーとなるBeanのクラスを設定する。<p>
         * 設定しない場合は、キーはRecordから{@link #setKeyPropertyName(String)}で設定されたプロパティ名で取得したオブジェクトとなる。<br>
         * 
         * @return キーとなるBeanのクラス
         */
        public void setKeyClass(Class<?> clazz){
            keyClass = clazz;
        }
        
        /**
         * 読み込んだRecordからキーのBeanへのプロパティマッピングを設定する。<p>
         * 
         * @param getProperty Recordから取得するプロパティ
         * @param setProperty Beanに設定するプロパティ
         */
        public void setKeyLoadPropertyMapping(String getProperty, String setProperty){
            if(keyLoadPropertyMappings == null){
                keyLoadPropertyMappings = new HashMap<String,String>();
            }
            keyLoadPropertyMappings.put(getProperty, setProperty);
        }
        
        /**
         * キーのBeanから値を取得して、Recordのプロパティへ設定するためのマッピングを設定する。<p>
         * 
         * @param getProperty Beanから取得するプロパティ
         * @param setProperty Recordに設定するプロパティ
         */
        public void setKeySavePropertyMapping(String getProperty, String setProperty){
            if(keySavePropertyMappings == null){
                keySavePropertyMappings = new HashMap<String,String>();
            }
            keySavePropertyMappings.put(getProperty, setProperty);
        }
        
        /**
         * 読み込み結果となるBeanのクラスを設定する。<p>
         * 設定しない場合は、読み込み結果はRecordとなる。<br>
         * 
         * @return 読み込み結果となるBeanのクラス
         */
        public void setValueClass(Class<?> clazz){
            valueClass = clazz;
        }
        
        /**
         * 読み込んだRecordからBeanへのプロパティマッピングを設定する。<p>
         * 
         * @param recordProperty Recordから取得するプロパティ名
         * @param setProperty Beanに設定するプロパティ
         */
        public void setLoadPropertyMapping(String recordProperty, String setProperty){
            setLoadPropertyMapping(recordProperty, recordProperty, setProperty);
        }
        
        /**
         * 読み込んだRecordからBeanへのプロパティマッピングを設定する。<p>
         * 
         * @param recordProperty Recordのプロパティ名
         * @param getProperty Recordから取得するプロパティ
         * @param setProperty Beanに設定するプロパティ
         */
        public void setLoadPropertyMapping(String recordProperty, String getProperty, String setProperty){
            if(loadPropertyMappings == null){
                loadPropertyMappings = new HashMap<String, Map<String, List<String>>>();
            }
            Map<String, List<String>> propMapping = loadPropertyMappings.get(recordProperty);
            if(propMapping == null){
                propMapping = new HashMap<String, List<String>>();
                loadPropertyMappings.put(recordProperty, propMapping);
            }
            List<String> setProperties = propMapping.get(getProperty);
            if(setProperties == null){
                setProperties = new ArrayList<String>();
                propMapping.put(getProperty, setProperties);
            }
            setProperties.add(setProperty);
        }
        
        /**
         * Beanから値を取得して、Recordのプロパティへ設定するためのマッピングを設定する。<p>
         * 
         * @param recordProperty Recordから取得するプロパティ名
         * @param setProperty Recordに設定するプロパティ
         */
        public void setSavePropertyMapping(String recordProperty, String setProperty){
            setSavePropertyMapping(recordProperty, recordProperty, setProperty);
        }
        
        /**
         * Beanから値を取得して、Recordのプロパティへ設定するためのマッピングを設定する。<p>
         * 
         * @param recordProperty Recordのプロパティ名
         * @param getProperty Beanから取得するプロパティ
         * @param setProperty Recordに設定するプロパティ
         */
        public void setSavePropertyMapping(String recordProperty, String getProperty, String setProperty){
            if(savePropertyMappings == null){
                savePropertyMappings = new HashMap<String, Map<String, List<String>>>();
            }
            Map<String, List<String>> propMapping = savePropertyMappings.get(recordProperty);
            if(propMapping == null){
                propMapping = new HashMap<String, List<String>>();
                savePropertyMappings.put(recordProperty, propMapping);
            }
            List<String> setProperties = propMapping.get(getProperty);
            if(setProperties == null){
                setProperties = new ArrayList<String>();
                propMapping.put(getProperty, setProperties);
            }
            setProperties.add(setProperty);
        }
        
        /**
         * リレーションテーブルを紐付けるための{@link DatabaseSubMapping}を設定する。<p>
         * 
         * @param mapping 
         */
        public void addSubMapping(DatabaseSubMapping mapping){
            if(subMappings == null){
                subMappings = new ArrayList<DatabaseSubMapping>();
            }
            subMappings.add(mapping);
        }
        
        /**
         * 読み込む際のフェッチサイズを設定する。<p>
         * 
         * @param fetchSize フェッチサイズ
         */
        public void setFetchSize(int fetchSize){
            this.fetchSize = fetchSize;
        }
        
        /**
         * {@link DatabaseSubMapping}が登録されている場合に、子カーソルを使用するかどうかを設定する。<p>
         * デフォルトは、true。<br>
         *
         * @param isUse 使用する場合true
         */
        public void setUseSubCursor(boolean isUse){
            isUseSubCursor = isUse;
        }
        
        /**
         * {@link DatabaseSubMapping}が登録されている場合に、子カーソルに対して別のJDBCコネクションを使用するかどうかを設定する。<p>
         * デフォルトは、false。<br>
         *
         * @param isUse 使用する場合true
         */
        public void setUseSubConnection(boolean isUse){
            isUseSubConnection = isUse;
        }
        
        /**
         * 保存する際のバッチ実行件数を設定する。<p>
         * 
         * @param count バッチ実行件数
         */
        public void setBatchPersistCount(int count){
            batchPersistCount = count;
        }
        
        /**
         * バッチ実行で保存する際に、バッチ実行時にコミットするかどうかを設定する。<p>
         * 
         * @param isCommit バッチ実行時にコミットする場合true
         */
        public void setBatchCommitOnPersist(boolean isCommit){
            isBatchCommitOnPersist = isCommit;
        }
        
        /**
         * {@link #setUniqueKey(boolean) setUniqueKey(false)}の場合に、値のリストをソートするかどうかを設定する。<p>
         * デフォルトは、falseでソートしない。<br>
         *
         * @param isSort ソートする場合、true
         */
        public void setValueSort(boolean isSort){
            this.isSort = isSort;
        }
        
        /**
         * {@link #setUniqueKey(boolean) setUniqueKey(false)}の場合に、値のレコードリストをソートするかどうかを設定する。<p>
         * 設定しない場合、ソートしない。<br>
         *
         * @param propNames ソートするプロパティ名
         * @param isAsc 昇順の場合、true
         */
        public void setValueRecordListSort(String[] propNames, boolean[] isAsc){
            isSort = true;
            sortPropertyNames = propNames;
            this.isAsc = isAsc;
        }
        
        public void load(SharedContext<K> context, ConnectionFactory factory, PersistentManager pm) throws Exception{
            if(parallelSelectQuery != null && parallelKeySelectQuery != null){
                loadParallel(context, factory, pm);
            }else if(selectQuery != null){
                load(context, factory, pm, selectQuery, null);
            }else{
                throw new IllegalArgumentException("Query is null.");
            }
        }
        
        protected void loadParallel(SharedContext<K> context, ConnectionFactory factory, PersistentManager pm) throws Exception{
            Connection con = null;
            List<?> inputList = null;
            try{
                con = factory.getConnection();
                if(databaseRecord == null){
                    inputList = new RecordList();
                }else{
                    inputList = new RecordList(null, databaseRecord.getRecordSchema());
                }
                pm.loadQuery(con, parallelKeySelectQuery, null, inputList);
            }finally{
                if(con != null){
                    try{
                        con.close();
                    }catch(SQLException e){}
                }
            }
            if(inputList.size() == 0){
                return;
            }
            if(parallelSize <= 1){
                for(int i = 0; i < inputList.size(); i++){
                    load(context, factory, pm, parallelSelectQuery, inputList.get(i));
                }
            }else{
                QueueHandlerContainerService<AsynchContext<Object[], ?>> qhc = new QueueHandlerContainerService<AsynchContext<Object[], ?>>();
                DefaultQueueService<AsynchContext<Object[], ?>> requestQueue = new DefaultQueueService<AsynchContext<Object[], ?>>();
                DefaultQueueService<AsynchContext<Object[], Object>> responseQueue = new DefaultQueueService<AsynchContext<Object[], Object>>();
                requestQueue.create();
                requestQueue.start();
                
                responseQueue.create();
                responseQueue.start();
                
                qhc.create();
                qhc.setQueueService(requestQueue);
                qhc.setDaemonQueueHandler(true);
                qhc.setQueueHandlerSize(parallelSize);
                qhc.setQueueHandler(new LoadQueueHandler());
                qhc.start();
                
                try{
                    for(int i = 0; i < inputList.size(); i++){
                        AsynchContext<Object[], ?> ac = new AsynchContext<Object[], Object>(
                            new Object[]{context, factory, pm, inputList.get(i)},
                            responseQueue
                        );
                        qhc.push(ac);
                    }
                    for(int i = 0, imax = inputList.size(); i < imax; i++){
                        AsynchContext<Object[], ?> ac = responseQueue.get();
                        if(ac == null){
                            throw new Exception("Break parallel load.");
                        }
                        try{
                            ac.checkError();
                        }catch(Throwable th){
                            if(th instanceof Exception){
                                throw (Exception)th;
                            }else{
                                throw (Error)th;
                            }
                        }
                    }
                }finally{
                    qhc.stop();
                }
            }
        }
        
        @SuppressWarnings("unchecked")
        protected void load(SharedContext<K> context, ConnectionFactory factory, PersistentManager pm, String query, Object input) throws Exception{
            Map<String,Object> statementProps = null;
            if(fetchSize != 0){
                statementProps = new HashMap<String,Object>();
                statementProps.put("FetchSize", new Integer(fetchSize));
            }
            PersistentManager.Cursor cursor = null;
            List<Connection> subConnections = null;
            Set<Connection> subConnectionSet = null;
            List<PersistentManager.Cursor> subCursors = null;
            Connection con = null;
            try{
                con = factory.getConnection();
                cursor = pm.createQueryCursor(con, query, input, statementProps, null);
                Object output = null;
                Record record = null;
                @SuppressWarnings("rawtypes")
                List list = null;
                Object key = null;
                Object preKey = null;
                if(subMappings != null && (isUseSubCursor || isUseSubConnection)){
                    if(isUseSubCursor){
                        subCursors = new ArrayList<PersistentManager.Cursor>();
                    }
                    for(Iterator<DatabaseSubMapping> itr = subMappings.iterator(); itr.hasNext();){
                        DatabaseSubMapping subMapping = itr.next();
                        Connection subCon = con;
                        if(isUseSubConnection){
                            if(subConnections == null){
                                subConnections = new ArrayList<Connection>();
                                subConnectionSet = new HashSet<Connection>();
                            }
                            subCon = factory.getConnection();
                            subConnections.add(subCon);
                            subConnectionSet.add(subCon);
                        }
                        if(isUseSubCursor){
                            if(subMapping.isCursorAvailable()){
                                subCursors.add(subMapping.createCursor(subCon, pm));
                            }else{
                                subCursors.add(null);
                            }
                        }
                    }
                }
                while(cursor.next()){
                    if(record == null){
                        if(databaseRecord == null){
                            if(valueClass == null){
                                record = new SharedContextRecord();
                                output = record;
                            }else{
                                output = valueClass.newInstance();
                            }
                        }else{
                            record = databaseRecord.cloneSchema();
                            output = record;
                        }
                    }else{
                        if(valueClass == null){
                            if(databaseRecord == null){
                                record = new SharedContextRecord();
                            }else{
                                record = databaseRecord.cloneSchema();
                            }
                        }else{
                            record.clear();
                        }
                        output = record;
                    }
                    output = cursor.load(output);
                    if(propertyAccess == null){
                        propertyAccess = new PropertyAccess();
                    }
                    if(keyClass == null){
                        key = propertyAccess.get(output, keyLoadPropertyName);
                    }else{
                        key = keyClass.newInstance();
                        for(Iterator<Map.Entry<String,String>> itr = keyLoadPropertyMappings.entrySet().iterator(); itr.hasNext();){
                            Map.Entry<String,String> propMapping = itr.next();
                            propertyAccess.set(
                                key, 
                                propMapping.getValue(),
                                propertyAccess.get(output, propMapping.getKey())
                            );
                        }
                    }
                    if(record != null && valueClass != null){
                        Object outputBean = valueClass.newInstance();
                        if(loadPropertyMappings == null){
                            PropertySchema[] propSchemata = record.getRecordSchema().getPropertySchemata();
                            for(int i = 0; i < propSchemata.length; i++){
                                Object value = record.getProperty(propSchemata[i].getName());
                                propertyAccess.set(outputBean, propSchemata[i].getName(), value);
                            }
                        }else{
                            PropertySchema[] propSchemata = record.getRecordSchema().getPropertySchemata();
                            for(int i = 0; i < propSchemata.length; i++){
                                Map<String,List<String>> propMappings = loadPropertyMappings.get(propSchemata[i].getName());
                                if(propMappings != null){
                                    for(Iterator<Map.Entry<String, List<String>>> itr = propMappings.entrySet().iterator(); itr.hasNext();){
                                        Map.Entry<String, List<String>> propMapping = itr.next();
                                        Object value = propertyAccess.get(output, propMapping.getKey());
                                        List<String> beanProperties = propMapping.getValue();
                                        for(int j = 0; j < beanProperties.size(); j++){
                                            propertyAccess.set(outputBean, beanProperties.get(j), value);
                                        }
                                    }
                                }
                            }
                        }
                        output = outputBean;
                    }
                    if(subMappings != null){
                        for(int i = 0; i < subMappings.size(); i++){
                            DatabaseSubMapping subMapping = (DatabaseSubMapping)subMappings.get(i);
                            Connection subCon = con;
                            if(isUseSubConnection){
                                subCon = (Connection)subConnections.get(i);
                            }
                            if(isUseSubCursor){
                                PersistentManager.Cursor subCursor = (PersistentManager.Cursor)subCursors.get(i);
                                if(subCursor == null){
                                    subMapping.load(subCon, pm, output, record == null ? output : record);
                                }else{
                                    if(subCursor.isClosed()){
                                        continue;
                                    }
                                    if(subConnectionSet.contains(subCon)){
                                        subConnectionSet.remove(subCon);
                                        if(!subCursor.next()){
                                            subCursor.close();
                                            try{
                                                subCon.close();
                                            }catch(SQLException e){}
                                            subConnections.set(i, null);
                                            continue;
                                        }
                                    }
                                    if(!subMapping.load(subCursor, output, record == null ? output : record)){
                                        subCursor.close();
                                        try{
                                            subCon.close();
                                        }catch(SQLException e){}
                                        subConnections.set(i, null);
                                    }
                                }
                            }else{
                                subMapping.load(subCon, pm, output, record == null ? output : record);
                            }
                        }
                    }
                    
                    if(isUniqueKey){
                        context.put((K)key, output);
                    }else{
                        if(preKey == null || key.equals(preKey)){
                            if(list == null){
                                if(valueClass == null){
                                    list =  databaseRecord == null
                                        ? new SharedContextRecordList(null, record.getRecordSchema())
                                            : (databaseRecordList == null ? new RecordList(null, databaseRecord.getRecordSchema()) : databaseRecordList.cloneSchema());
                                }else{
                                    list = new ArrayList<Object>();
                                }
                            }
                            list.add(output);
                        }else{
                            if(isSort){
                                if(valueClass == null){
                                    ((RecordList)list).sort(sortPropertyNames, isAsc);
                                }else{
                                    Collections.sort(list);
                                }
                            }
                            context.put((K)key, list);
                            if(valueClass == null){
                                list =  databaseRecord == null
                                    ? new SharedContextRecordList(null, record.getRecordSchema())
                                        : (databaseRecordList == null ? new RecordList(null, databaseRecord.getRecordSchema()) : databaseRecordList.cloneSchema());
                            }else{
                                list = new ArrayList<Object>();
                            }
                        }
                        preKey = key;
                    }
                }
                if(!isUniqueKey && list != null && list.size() != 0){
                    if(isSort){
                        if(valueClass == null){
                            ((RecordList)list).sort(sortPropertyNames, isAsc);
                        }else{
                            Collections.sort(list);
                        }
                    }
                    context.put((K)key, list);
                }
            }finally{
                if(subCursors != null){
                    for(int i = 0; i < subCursors.size(); i++){
                        ((PersistentManager.Cursor)subCursors.get(i)).close();
                    }
                    if(subConnections != null){
                        for(int i = 0; i < subConnections.size(); i++){
                            Connection subCon = (Connection)subConnections.get(i);
                            if(subCon != null){
                                try{
                                    subCon.close();
                                }catch(SQLException e){}
                            }
                        }
                    }
                }
                if(cursor != null){
                    cursor.close();
                }
                if(con != null){
                    try{
                        con.close();
                    }catch(SQLException e){}
                }
            }
        }
        
        public void clear(ConnectionFactory factory, PersistentManager pm) throws Exception{
            if(deleteQuery == null){
                throw new IllegalArgumentException("Query is null.");
            }
            Connection con = null;
            try{
                con = factory.getConnection();
                if(deleteQuery != null){
                    pm.persistQuery(con, deleteQuery, null);
                    if(subMappings != null){
                        for(int i = 0; i < subMappings.size(); i++){
                            DatabaseSubMapping subMapping = (DatabaseSubMapping)subMappings.get(i);
                            if(subMapping.getDeleteQuery() != null){
                                subMapping.delete(con, pm);
                            }
                        }
                    }
                }
            }finally{
                if(con != null){
                    try{
                        con.close();
                    }catch(SQLException e){}
                }
            }
        }
        
        public void save(SharedContext<K> context, ConnectionFactory factory, PersistentManager pm) throws Exception{
            if(insertQuery == null){
                throw new IllegalArgumentException("Query is null.");
            }
            
            if(context.size() == 0){
                return;
            }
            Connection con = null;
            PersistentManager.BatchExecutor executor = null;
            try{
                Object[] keys = null;
                con = factory.getConnection();
                if(insertKeySelectQuery != null){
                    List<Record> keyList = null;
                    if(databaseRecord == null){
                        if(keyClass == null){
                            keyList = new RecordList();
                        }
                    }else{
                        keyList = new RecordList(null, databaseRecord.getRecordSchema());
                    }
                    Object[] keyRecords = null;
                    if(keyList == null){
                        keyRecords = (Object[])pm.loadQuery(con, insertKeySelectQuery, null, Array.newInstance(keyClass, 0).getClass());
                    }else{
                        pm.loadQuery(con, insertKeySelectQuery, null, keyList);
                        keyRecords = keyList.toArray();
                    }
                    if(keyRecords == null || keyRecords.length == 0){
                        return;
                    }
                    if(keyList == null){
                        keys = keyRecords;
                    }else{
                        keys = new Object[keyRecords.length];
                        for(int i = 0; i < keyRecords.length; i++){
                            if(keyClass == null){
                                keys[i] = propertyAccess.get(keyRecords[i], keyLoadPropertyName);
                            }else{
                                keys[i] = keyClass.newInstance();
                                for(Iterator<Map.Entry<String,String>> itr = keyLoadPropertyMappings.entrySet().iterator(); itr.hasNext();){
                                    Map.Entry<String,String> propMapping = itr.next();
                                    propertyAccess.set(
                                        keys[i], 
                                        (String)propMapping.getValue(),
                                        propertyAccess.get(keyRecords[i], (String)propMapping.getKey())
                                    );
                                }
                            }
                        }
                    }
                }else{
                    keys = context.keySet().toArray();
                }
                
                executor = pm.createQueryBatchExecutor(con, insertQuery);
                if(batchPersistCount > 0){
                    executor.setAutoBatchPersistCount(batchPersistCount);
                    executor.setAutoCommitOnPersist(isBatchCommitOnPersist);
                }
                Record record = null;
                for(int i = 0; i < keys.length; i++){
                    Object bean = context.get(keys[i]);
                    if(isUniqueKey){
                        record = save(con, pm, executor, keys[i], bean, record);
                    }else if(bean != null){
                        @SuppressWarnings("unchecked")
                        List<Object> list = (List<Object>)bean;
                        for(int j = 0; j < list.size(); j++){
                            record = save(con, pm, executor, keys[i], list.get(i), record);
                        }
                    }
                }
                executor.persist();
            }finally{
                if(executor != null){
                    executor.close();
                }
                if(con != null){
                    try{
                        con.close();
                    }catch(SQLException e){}
                }
            }
        }
        
        private Record save(Connection con, PersistentManager pm, PersistentManager.BatchExecutor executor, Object key, Object bean, Record record) throws Exception{
            Object input = null;
            if(databaseRecord == null){
                input = bean;
            }else{
                if(propertyAccess == null){
                    propertyAccess = new PropertyAccess();
                }
                if(record == null){
                    record = databaseRecord.cloneSchema();
                }else{
                    record.clear();
                }
                PropertySchema[] propSchemata = record.getRecordSchema().getPropertySchemata();
                if(keyClass == null){
                    propertyAccess.set(record, keySavePropertyName, key);
                }else{
                    for(Iterator<Map.Entry<String,String>> itr = keySavePropertyMappings.entrySet().iterator(); itr.hasNext();){
                        Map.Entry<String,String> propMapping = itr.next();
                        propertyAccess.set(
                            record, 
                            propMapping.getValue(),
                            propertyAccess.get(key, propMapping.getKey())
                        );
                    }
                }
                if(savePropertyMappings == null){
                    for(int j = 0; j < propSchemata.length; j++){
                        Object value = propertyAccess.get(bean, propSchemata[j].getName());
                        record.setProperty(propSchemata[j].getName(), value);
                    }
                }else{
                    for(int j = 0; j < propSchemata.length; j++){
                        Map<String, List<String>> propMappings = savePropertyMappings.get(propSchemata[j].getName());
                        if(propMappings != null){
                            for(Iterator<Map.Entry<String, List<String>>> itr = propMappings.entrySet().iterator(); itr.hasNext();){
                                Map.Entry<String, List<String>> propMapping = itr.next();
                                Object value = propertyAccess.get(bean, propMapping.getKey());
                                List<String> recordProperties = propMapping.getValue();
                                for(int k = 0; k < recordProperties.size(); k++){
                                    propertyAccess.set(record, recordProperties.get(k), value);
                                }
                            }
                        }
                    }
                }
                input = record;
            }
            executor.addBatch(input);
            
            if(subMappings != null){
                for(int j = 0; j < subMappings.size(); j++){
                    DatabaseSubMapping subMapping = (DatabaseSubMapping)subMappings.get(j);
                    if(subMapping.getInsertQuery() != null){
                        subMapping.insert(con, pm, input, bean);
                    }
                }
            }
            return record;
        }
        
        private class LoadQueueHandler implements QueueHandler<AsynchContext<Object[], ?>>{
            @SuppressWarnings("unchecked")
            public void handleDequeuedObject(AsynchContext<Object[], ?> ac) throws Throwable{
                if(ac == null){
                    return;
                }
                Object[] params = (Object[])ac.getInput();
                load((SharedContext<K>)params[0], (ConnectionFactory)params[1], (PersistentManager)params[2], parallelSelectQuery, params[3]);
                ac.response();
            }
            public boolean handleError(AsynchContext<Object[], ?> ac, Throwable th) throws Throwable{
                return false;
            }
            public void handleRetryOver(AsynchContext<Object[], ?> ac, Throwable th) throws Throwable{
                ac.setThrowable(th);
                ac.response();
            }
        }
    }
    
    public static class DatabaseSubMapping implements Serializable{
        
        private static final long serialVersionUID = -8734674911693127987L;
        
        public static final String QUERY_KEY_PARENT = "parent";
        public static final String QUERY_KEY_THIS = "this";
        
        protected String selectQuery;
        protected String insertQuery;
        protected String deleteQuery;
        protected Record databaseRecord;
        protected Map<String,Map<String, List<String>>> loadPropertyMappings;
        protected Map<String,Map<String, List<String>>> savePropertyMappings;
        protected Map<String,String> keyPropertyMappings;
        protected String indexProperty;
        protected int fetchSize;
        protected int batchPersistCount;
        protected boolean isBatchCommitOnPersist = true;
        
        protected Class<?> beanClass;
        protected String beanPropertyOfParent;
        protected String beansPropertyOfParent;
        protected PropertyAccess propertyAccess;
        
        /**
         * 読み込みに使用する検索クエリを設定する。<p>
         *
         * @param query クエリ
         */
        public void setSelectQuery(String query){
            selectQuery = query;
        }
        public String getSelectQuery(){
            return selectQuery;
        }
        
        /**
         * 保存の際に使用する挿入クエリを設定する。<p>
         *
         * @param query クエリ
         */
        public void setInsertQuery(String query){
            insertQuery = query;
        }
        public String getInsertQuery(){
            return insertQuery;
        }
        
        /**
         * 削除の際に使用する削除クエリを設定する。<p>
         *
         * @param query クエリ
         */
        public void setDeleteQuery(String query){
            deleteQuery = query;
        }
        public String getDeleteQuery(){
            return deleteQuery;
        }
        
        /**
         * PersistentManagerに渡すRecordオブジェクトを設定する。<p>
         * Beanに直接マッピングする場合には、設定する必要はない。<br>
         * 
         * @param record 
         */
        public void setDatabaseRecord(Record record){
            this.databaseRecord = record;
        }
        
        /**
         * 読み込んだRecordからBeanへのプロパティマッピングを設定する。<p>
         * 
         * @param recordProperty Recordから取得するプロパティ名
         * @param setProperty Beanに設定するプロパティ
         */
        public void setLoadPropertyMapping(String recordProperty, String setProperty){
            setLoadPropertyMapping(recordProperty, recordProperty, setProperty);
        }
        
        /**
         * 読み込んだRecordからBeanへのプロパティマッピングを設定する。<p>
         * 
         * @param recordProperty Recordから取得するプロパティ名
         * @param getProperty Recordから取得するプロパティ
         * @param setProperty Beanに設定するプロパティ
         */
        public void setLoadPropertyMapping(String recordProperty, String getProperty, String setProperty){
            if(loadPropertyMappings == null){
                loadPropertyMappings = new HashMap<String,Map<String, List<String>>>();
            }
            Map<String, List<String>> propMapping = loadPropertyMappings.get(recordProperty);
            if(propMapping == null){
                propMapping = new HashMap<String, List<String>>();
                loadPropertyMappings.put(recordProperty, propMapping);
            }
            List<String> setProperties = propMapping.get(getProperty);
            if(setProperties == null){
                setProperties = new ArrayList<String>();
                propMapping.put(getProperty, setProperties);
            }
            if(setProperty != null){
                setProperties.add(setProperty);
            }
        }
        
        /**
         * Beanから値を取得して、Recordのプロパティへ設定するためのマッピングを設定する。<p>
         * 
         * @param recordProperty Recordから取得するプロパティ名
         * @param setProperty Recordに設定するプロパティ
         */
        public void setSavePropertyMapping(String recordProperty, String setProperty){
            setSavePropertyMapping(recordProperty, recordProperty, setProperty);
        }
        
        /**
         * Beanから値を取得して、Recordのプロパティへ設定するためのマッピングを設定する。<p>
         * 
         * @param recordProperty Recordから取得するプロパティ名
         * @param getProperty Beanから取得するプロパティ
         * @param setProperty Recordに設定するプロパティ
         */
        public void setSavePropertyMapping(String recordProperty, String getProperty, String setProperty){
            if(savePropertyMappings == null){
                savePropertyMappings = new HashMap<String,Map<String, List<String>>>();
            }
            Map<String, List<String>> propMapping = savePropertyMappings.get(recordProperty);
            if(propMapping == null){
                propMapping = new HashMap<String, List<String>>();
                savePropertyMappings.put(recordProperty, propMapping);
            }
            List<String> setProperties = propMapping.get(getProperty);
            if(setProperties == null){
                setProperties = new ArrayList<String>();
                propMapping.put(getProperty, setProperties);
            }
            if(setProperty != null){
                setProperties.add(setProperty);
            }
        }
        
        public void setKeyPropertyMappings(Map<String,String> mappings){
            keyPropertyMappings = mappings;
        }
        
        public void setKeyPropertyMapping(String beanProperty, String recordProperty){
            if(keyPropertyMappings == null){
                keyPropertyMappings = new HashMap<String,String>();
            }
            keyPropertyMappings.put(beanProperty, recordProperty);
        }
        
        public void setIndexProperty(String recordProperty){
            indexProperty = recordProperty;
        }
        
        public boolean isCursorAvailable(){
            return keyPropertyMappings != null && keyPropertyMappings.size() != 0;
        }
        
        public void setFetchSize(int fetchSize){
            this.fetchSize = fetchSize;
        }
        
        public void setBatchPersistCount(int count){
            this.batchPersistCount = count;
        }
        
        public void setBeanClass(Class<?> clazz, String property){
            beanClass = clazz;
            beanPropertyOfParent = property;
        }
        
        public void setBeansProperty(String property){
            beansPropertyOfParent = property;
        }
        
        public PersistentManager.Cursor createCursor(Connection con, PersistentManager pm) throws Exception{
            Map<String,Object> statementProps = null;
            if(fetchSize != 0){
                statementProps = new HashMap<String,Object>();
                statementProps.put("FetchSize", new Integer(fetchSize));
            }
            return pm.createQueryCursor(con, selectQuery, null, statementProps, null);
        }
        
        public boolean load(PersistentManager.Cursor cursor, Object parent, Object keyObject) throws Exception{
            if(propertyAccess == null){
                propertyAccess = new PropertyAccess();
            }
            Map<String, Object> keyValueMap = null;
            if(keyPropertyMappings != null){
                for(Iterator<Map.Entry<String, String>> itr = keyPropertyMappings.entrySet().iterator(); itr.hasNext();){
                    Map.Entry<String, String> entry = itr.next();
                    if(keyValueMap == null){
                        keyValueMap = new HashMap<String, Object>();
                    }
                    keyValueMap.put(entry.getValue(), propertyAccess.get(keyObject, entry.getKey()));
                }
            }
            Record record = null;
            do{
                if(record == null){
                    if(databaseRecord == null){
                        record = new Record();
                    }else{
                        record = databaseRecord.cloneSchema();
                    }
                }else{
                    record.clear();
                }
                cursor.load(record);
                if(keyValueMap != null){
                    for(Iterator<Map.Entry<String,Object>> itr = keyValueMap.entrySet().iterator(); itr.hasNext();){
                    Map.Entry<String,Object> entry = itr.next();
                        Object val = propertyAccess.get(record, entry.getKey());
                        if((val == null && entry.getValue() != null)
                            || (val != null && entry.getValue() == null)
                            || (val != null && !val.equals(entry.getValue()))){
                            return true;
                        }
                    }
                }
                int index = 0;
                if(indexProperty != null){
                    index = record.getIntProperty(indexProperty);
                }
                Object loadBean = parent;
                if(beanClass != null){
                    loadBean = beanClass.newInstance();
                }
                if(loadPropertyMappings == null){
                    cursor.load(loadBean);
                }else{
                    PropertySchema[] propSchemata = record.getRecordSchema().getPropertySchemata();
                    for(int i = 0; i < propSchemata.length; i++){
                        Map<String, List<String>> propMappings = loadPropertyMappings.get(propSchemata[i].getName());
                        if(propMappings != null){
                            for(Iterator<Map.Entry<String, List<String>>> itr = propMappings.entrySet().iterator(); itr.hasNext();){
                                Map.Entry<String, List<String>> propMapping = itr.next();
                                Object value = propertyAccess.get(record, propMapping.getKey());
                                List<String> beanProperties = propMapping.getValue();
                                for(int j = 0; j < beanProperties.size(); j++){
                                    if(indexProperty == null){
                                        propertyAccess.set(loadBean, beanProperties.get(j), value);
                                    }else{
                                        Property prop = propertyAccess.getProperty(beanProperties.get(j));
                                        if(prop instanceof IndexedProperty){
                                            IndexedProperty indexedProp = (IndexedProperty)prop;
                                            indexedProp.setIndex(index);
                                        }
                                        prop.setProperty(loadBean, value);
                                    }
                                }
                            }
                        }
                    }
                }
                if(beanClass != null){
                    if(indexProperty == null){
                        propertyAccess.set(parent, beanPropertyOfParent, loadBean);
                    }else{
                        Property prop = propertyAccess.getProperty(beanPropertyOfParent);
                        if(prop instanceof IndexedProperty){
                            IndexedProperty indexedProp = (IndexedProperty)prop;
                            indexedProp.setIndex(index);
                        }
                        prop.setProperty(parent, loadBean);
                    }
                    propertyAccess.set(parent, beanPropertyOfParent, loadBean);
                }
            }while(cursor.next());
            return false;
        }
        
        public Object load(Connection con, PersistentManager pm, Object parent, Object keyObject) throws Exception{
            
            Map<String,Object> statementProps = null;
            if(fetchSize != 0){
                statementProps = new HashMap<String,Object>();
                statementProps.put("FetchSize", new Integer(fetchSize));
            }
            PersistentManager.Cursor cursor = null;
            try{
                cursor = pm.createQueryCursor(con, selectQuery, keyObject, statementProps, null);
                Object output = null;
                Record record = null;
                while(cursor.next()){
                    if(databaseRecord == null){
                        output = parent; 
                    }else{
                        if(record == null){
                            record = databaseRecord.cloneSchema();
                        }else{
                            record.clear();
                        }
                        output = record;
                    }
                    output = cursor.load(output);
                    if(record != null && loadPropertyMappings != null){
                        if(propertyAccess == null){
                            propertyAccess = new PropertyAccess();
                        }
                        int index = 0;
                        if(indexProperty != null){
                            index = record.getIntProperty(indexProperty);
                        }
                        Object loadBean = parent;
                        if(beanClass != null){
                            loadBean = beanClass.newInstance();
                            if(indexProperty == null){
                                propertyAccess.set(parent, beanPropertyOfParent, loadBean);
                            }else{
                                Property prop = propertyAccess.getProperty(beanPropertyOfParent);
                                if(prop instanceof IndexedProperty){
                                    IndexedProperty indexedProp = (IndexedProperty)prop;
                                    indexedProp.setIndex(index);
                                }
                                prop.setProperty(parent, loadBean);
                            }
                            propertyAccess.set(parent, beanPropertyOfParent, loadBean);
                        }
                        PropertySchema[] propSchemata = record.getRecordSchema().getPropertySchemata();
                        for(int i = 0; i < propSchemata.length; i++){
                            Map<String,List<String>> propMappings = loadPropertyMappings.get(propSchemata[i].getName());
                            if(propMappings != null){
                                for(Iterator<Map.Entry<String,List<String>>> itr = propMappings.entrySet().iterator(); itr.hasNext();){
                                    Map.Entry<String,List<String>> propMapping = itr.next();
                                    Object value = propertyAccess.get(record, propMapping.getKey());
                                    List<String> beanProperties = propMapping.getValue();
                                    for(int j = 0; j < beanProperties.size(); j++){
                                        if(indexProperty == null){
                                            propertyAccess.set(loadBean, beanProperties.get(j), value);
                                        }else{
                                            Property prop = propertyAccess.getProperty(beanProperties.get(j));
                                            if(prop instanceof IndexedProperty){
                                                IndexedProperty indexedProp = (IndexedProperty)prop;
                                                indexedProp.setIndex(index);
                                            }
                                            prop.setProperty(loadBean, value);
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }finally{
                if(cursor != null){
                    cursor.close();
                }
            }
            return parent;
        }
        
        public int delete(Connection con, PersistentManager pm) throws Exception{
            if(deleteQuery == null){
                return 0;
            }
            return pm.persistQuery(con, deleteQuery, null);
        }
        
        private int getArrayLength(Object value){
            if(value == null){
                return -1;
            }
            if(value instanceof Collection){
                return ((Collection<?>)value).size();
            }else if(value.getClass().isArray()){
                return Array.getLength(value);
            }else{
                return -1;
            }
        }
        
        public int insert(Connection con, PersistentManager pm, Object parentRecord, Object parent) throws Exception{
            if(insertQuery == null){
                return 0;
            }
            if(parent == null){
                return 0;
            }
            PersistentManager.BatchExecutor executor = null;
            try{
                executor = pm.createQueryBatchExecutor(con, insertQuery);
                if(batchPersistCount > 0){
                    executor.setAutoBatchPersistCount(batchPersistCount);
                    executor.setAutoCommitOnPersist(isBatchCommitOnPersist);
                }
                Map<String,Object> inputMap = new HashMap<String,Object>();
                inputMap.put(QUERY_KEY_PARENT, parentRecord);
                Object input = null;
                Record record = null;
                int persistCount = 0;
                if(databaseRecord == null){
                    inputMap.put(QUERY_KEY_THIS, input);
                    persistCount += executor.addBatch(inputMap);
                    persistCount += executor.persist();
                }else{
                    if(propertyAccess == null){
                        propertyAccess = new PropertyAccess();
                    }
                    record = databaseRecord.cloneSchema();
                    int maxLength = -1;
                    PropertySchema[] propSchemata = record.getRecordSchema().getPropertySchemata();
                    if(savePropertyMappings == null){
                        Object persistBean = parent;
                        if(beansPropertyOfParent != null){
                            persistBean = propertyAccess.get(parent, beansPropertyOfParent);
                            maxLength = getArrayLength(persistBean);
                        }else{
                            for(int i = 0; i < propSchemata.length; i++){
                                if(indexProperty != null && indexProperty.equals(propSchemata[i].getName())){
                                    continue;
                                }
                                Object value = propertyAccess.get(parent, propSchemata[i].getName());
                                int arrayLength = getArrayLength(value);
                                if(arrayLength == -1){
                                    continue;
                                }
                                if(maxLength < arrayLength){
                                    maxLength = arrayLength;
                                }
                            }
                        }
                        if(maxLength == -1){
                            record.clear();
                            for(int i = 0; i < propSchemata.length; i++){
                                Object value = propertyAccess.get(persistBean, propSchemata[i].getName());
                                propertyAccess.set(record, propSchemata[i].getName(), value);
                            }
                            inputMap.put(QUERY_KEY_THIS, record);
                            persistCount += executor.addBatch(inputMap);
                            persistCount += executor.persist();
                        }else{
                            for(int i = 0; i < maxLength; i++){
                                record.clear();
                                if(indexProperty != null){
                                    propertyAccess.set(record, indexProperty, new Integer(i));
                                }
                                if(beansPropertyOfParent != null){
                                    Object bean = null;
                                    if(persistBean instanceof List){
                                        bean = ((List<?>)persistBean).get(i);
                                    }else if(persistBean instanceof Collection){
                                        bean = ((Collection<?>)persistBean).toArray()[i];
                                    }else if(persistBean.getClass().isArray()){
                                        bean = Array.get(persistBean, i);
                                    }
                                    if(bean == null){
                                        continue;
                                    }
                                    for(int j = 0; j < propSchemata.length; j++){
                                        Object value = propertyAccess.get(bean, propSchemata[j].getName());
                                        propertyAccess.set(record, propSchemata[j].getName(), value);
                                    }
                                }else{
                                    for(int j = 0; j < propSchemata.length; j++){
                                        Object value = propertyAccess.get(persistBean, propSchemata[j].getName());
                                        if(value == null){
                                            propertyAccess.set(record, propSchemata[j].getName(), null);
                                            continue;
                                        }
                                        Object element = null;
                                        if(value instanceof Collection){
                                            if(((Collection<?>)value).size() > i){
                                                if(value instanceof List){
                                                    element = ((List<?>)value).get(i);
                                                }else{
                                                    element = Array.get(((Collection<?>)value).toArray(), i);
                                                }
                                            }
                                        }else if(value.getClass().isArray()){
                                            if(Array.getLength(value) > i){
                                                element = Array.get(value, i);
                                            }
                                        }else{
                                            propertyAccess.set(record, propSchemata[j].getName(), value);
                                            continue;
                                        }
                                        propertyAccess.set(record, propSchemata[j].getName(), element);
                                    }
                                }
                                inputMap.put(QUERY_KEY_THIS, record);
                                persistCount += executor.addBatch(inputMap);
                            }
                            persistCount += executor.persist();
                        }
                    }else{
                        Object persistBean = parent;
                        if(beansPropertyOfParent != null){
                            persistBean = propertyAccess.get(parent, beansPropertyOfParent);
                            maxLength = getArrayLength(persistBean);
                        }else{
                            for(int i = 0; i < propSchemata.length; i++){
                                if(indexProperty != null && indexProperty.equals(propSchemata[i].getName())){
                                    continue;
                                }
                                Map<String,List<String>> propMappings = savePropertyMappings.get(propSchemata[i].getName());
                                if(propMappings != null){
                                    for(Iterator<String> itr = propMappings.keySet().iterator(); itr.hasNext();){
                                        String key = itr.next();
                                        Object value = propertyAccess.get(parent, key);
                                        int arrayLength = getArrayLength(value);
                                        if(arrayLength == -1){
                                            continue;
                                        }
                                        if(maxLength < arrayLength){
                                            maxLength = arrayLength;
                                        }
                                    }
                                }
                            }
                        }
                        if(maxLength == -1){
                            record = databaseRecord.cloneSchema();
                            for(int i = 0; i < propSchemata.length; i++){
                                Map<String,List<String>> propMappings = savePropertyMappings.get(propSchemata[i].getName());
                                if(propMappings != null){
                                    for(Iterator<Map.Entry<String,List<String>>> itr = propMappings.entrySet().iterator(); itr.hasNext();){
                                        Map.Entry<String,List<String>> propMapping = itr.next();
                                        Object value = propertyAccess.get(persistBean, propMapping.getKey());
                                        List<String> recordProperties = propMapping.getValue();
                                        for(int j = 0; j < recordProperties.size(); j++){
                                            propertyAccess.set(record, recordProperties.get(j), value);
                                        }
                                    }
                                }
                            }
                            inputMap.put(QUERY_KEY_THIS, record);
                            persistCount += executor.addBatch(inputMap);
                            persistCount += executor.persist();
                        }else{
                            for(int i = 0; i < maxLength; i++){
                                if(record == null){
                                    record = databaseRecord.cloneSchema();
                                }else{
                                    record.clear();
                                }
                                if(indexProperty != null){
                                    propertyAccess.set(record, indexProperty, new Integer(i));
                                }
                                if(beansPropertyOfParent != null){
                                    Object bean = null;
                                    if(persistBean instanceof List){
                                        bean = ((List<?>)persistBean).get(i);
                                    }else if(persistBean instanceof Collection){
                                        bean = ((Collection<?>)persistBean).toArray()[i];
                                    }else if(persistBean.getClass().isArray()){
                                        bean = Array.get(persistBean, i);
                                    }
                                    if(bean == null){
                                        continue;
                                    }
                                    for(int j = 0; j < propSchemata.length; j++){
                                        Map<String,List<String>> propMappings = savePropertyMappings.get(propSchemata[j].getName());
                                        if(propMappings != null){
                                            for(Iterator<Map.Entry<String,List<String>>> itr = propMappings.entrySet().iterator(); itr.hasNext();){
                                                Map.Entry<String,List<String>> propMapping = itr.next();
                                                Object value = propertyAccess.get(bean, propMapping.getKey());
                                                List<String> recordProperties = propMapping.getValue();
                                                for(int k = 0; k < recordProperties.size(); k++){
                                                    propertyAccess.set(record, recordProperties.get(k), value);
                                                }
                                            }
                                        }
                                    }
                                }else{
                                    for(int j = 0; j < propSchemata.length; j++){
                                        Map<String,List<String>> propMappings = savePropertyMappings.get(propSchemata[j].getName());
                                        if(propMappings != null){
                                            for(Iterator<Map.Entry<String,List<String>>> itr = propMappings.entrySet().iterator(); itr.hasNext();){
                                                Map.Entry<String,List<String>> propMapping = itr.next();
                                                Object value = propertyAccess.get(persistBean, propMapping.getKey());
                                                if(value == null){
                                                    List<String> recordProperties = propMapping.getValue();
                                                    for(int k = 0; k < recordProperties.size(); k++){
                                                        propertyAccess.set(record, recordProperties.get(k), null);
                                                    }
                                                    continue;
                                                }
                                                Object element = null;
                                                if(value instanceof Collection){
                                                    if(((Collection<?>)value).size() > i){
                                                        if(value instanceof List){
                                                            element = ((List<?>)value).get(i);
                                                        }else{
                                                            element = Array.get(((Collection<?>)value).toArray(), i);
                                                        }
                                                    }
                                                }else if(value.getClass().isArray()){
                                                    if(Array.getLength(value) > i){
                                                        element = Array.get(value, i);
                                                    }
                                                }else{
                                                    element = value;
                                                }
                                                List<String> recordProperties = propMapping.getValue();
                                                for(int k = 0; k < recordProperties.size(); k++){
                                                    propertyAccess.set(record, recordProperties.get(k), element);
                                                }
                                            }
                                        }
                                    }
                                    inputMap.put(QUERY_KEY_THIS, record);
                                    persistCount += executor.addBatch(inputMap);
                                }
                            }
                            persistCount += executor.persist();
                        }
                    }
                }
                return persistCount;
            }finally{
                if(executor != null){
                    executor.close();
                }
            }
        }
    }
}
