001/*
002 *  Copyright 2018 Anyware Services
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package org.ametys.plugins.contentio.export.sql;
017
018import java.io.IOException;
019import java.sql.Connection;
020import java.sql.PreparedStatement;
021import java.sql.SQLException;
022import java.util.ArrayList;
023import java.util.HashMap;
024import java.util.LinkedHashMap;
025import java.util.List;
026import java.util.Map;
027import java.util.Map.Entry;
028import java.util.regex.Matcher;
029import java.util.regex.Pattern;
030
031import org.apache.avalon.framework.component.Component;
032import org.apache.avalon.framework.service.ServiceException;
033import org.apache.avalon.framework.service.ServiceManager;
034import org.apache.avalon.framework.service.Serviceable;
035import org.apache.commons.lang.StringUtils;
036
037import org.ametys.cms.contenttype.ContentType;
038import org.ametys.cms.contenttype.ContentTypeExtensionPoint;
039import org.ametys.cms.data.type.ModelItemTypeConstants;
040import org.ametys.cms.languages.LanguagesManager;
041import org.ametys.core.datasource.ConnectionHelper;
042import org.ametys.core.util.I18nUtils;
043import org.ametys.plugins.repository.model.CompositeDefinition;
044import org.ametys.plugins.repository.model.RepeaterDefinition;
045import org.ametys.runtime.config.Config;
046import org.ametys.runtime.i18n.I18nizableText;
047import org.ametys.runtime.model.ElementDefinition;
048import org.ametys.runtime.model.ModelItem;
049import org.ametys.runtime.model.ModelItemContainer;
050import org.ametys.runtime.plugin.component.AbstractLogEnabled;
051
052/**
053 *  Create SQl Table Component
054 */
055public class CreateSqlTableComponent extends AbstractLogEnabled implements Component, Serviceable
056{
057    /** The component role */
058    public static final String ROLE = CreateSqlTableComponent.class.getName();
059    
060    /** Code of default language for comments */
061    public static final String DEFAULT_LANGUAGE_CODE_FOR_COMMENTS = "en";
062    
063    /** The engine */
064    public static final String MYSQL_CONTENT_EXPORT_ENGINE = "MyISAM";
065    
066    /** The encoding */
067    public static final String MYSQL_CONTENT_EXPORT_CHARSET = "UTF8MB4";
068    
069    /** Prefix for parent table column */
070    public static final String COLUMN_PARENT_TABLE_PREFIX = "PID_";
071    
072    private static final Pattern _MYSQL_VERSION_NUMBER_EXTRACT = Pattern.compile("^([0-9]+).*$");
073    
074    /** Content type extension point. */
075    protected ContentTypeExtensionPoint _contentTypeExtensionPoint;
076    
077    /** The i18n translator. */
078    protected I18nUtils _i18nTranslator;
079    
080    /** The normalise name component. */
081    protected NormalizeNameComponent _normalizeNameComponent;
082    
083    /** The language manager */
084    protected LanguagesManager _languageManager;
085    
086    private Connection _connection;
087    private String _databaseType;
088    
089    private LinkedHashMap<String, ExportTableInfo> _tablesInfos;
090    private int _commentTableMaxLength;
091    private int _commentColumnMaxLength;
092    private String _sqlTablePrefix;
093    private String _sqlPrefixConf;
094    private Map<String, Map<String, String>> _mapping;
095    private Map<String, Map<String, String>> _reservedWords;
096    private String _mappingPolicy;
097    private ArrayList<String> _mappingTablesQueries;
098    private boolean _exportNoMultiValuedTable;
099    
100    private int _fkIndice;
101    private int _pkIndice;
102    
103    @Override
104    public void service(ServiceManager manager) throws ServiceException
105    {
106        _i18nTranslator = (I18nUtils) manager.lookup(I18nUtils.ROLE);
107        _contentTypeExtensionPoint = (ContentTypeExtensionPoint) manager.lookup(ContentTypeExtensionPoint.ROLE);
108        _normalizeNameComponent = (NormalizeNameComponent) manager.lookup(NormalizeNameComponent.ROLE);
109        _languageManager = (LanguagesManager) manager.lookup(LanguagesManager.ROLE);
110    } 
111    
112    /**
113     * Create sql tables for contents
114     * @param exportConfiguration the content export configuration
115     * @return tablesInfos
116     * @throws SQLException if a sql error occurred
117     * @throws IOException if an IO error occurred
118     */
119    public synchronized Map<String, ExportTableInfo> createTables(ExportConfiguration exportConfiguration) throws SQLException, IOException
120    {
121        // Get from configuration
122        _sqlPrefixConf = exportConfiguration.getTablePrefix();
123        _sqlTablePrefix = exportConfiguration.getTablePrefix();
124        _mapping = exportConfiguration.getMappingSql();
125        _mappingPolicy = exportConfiguration.getMappingPolicy();
126        _reservedWords = exportConfiguration.getReservedWords();
127        _exportNoMultiValuedTable = exportConfiguration.exportNoMultiValuedTable();
128        
129        // Initialization
130        _fkIndice = 1;
131        _pkIndice = 1;
132        _tablesInfos = new LinkedHashMap<>();
133        _mappingTablesQueries = new ArrayList<>();
134        
135        try
136        {
137            String datasourceId = Config.getInstance().getValue("org.ametys.plugins.contentio.content.export.datasource");
138            _connection = ConnectionHelper.getConnection(datasourceId); 
139            _databaseType = ConnectionHelper.getDatabaseType(_connection);
140            
141            String productVersion = _connection.getMetaData().getDatabaseProductVersion();
142            initialize(productVersion);
143            
144            boolean isInfoEnabled = getLogger().isInfoEnabled();
145            if (isInfoEnabled)
146            {
147                getLogger().info(_i18nTranslator.translate(new I18nizableText("plugin.contentio", "PLUGINS_CONTENTIO_CONTENT_EXPORT_LOG_ANALYZE_TABLE_CREATE_BEGIN")));
148            }
149            
150            createTablesInfos(exportConfiguration.getContentTypesToExport());
151            
152            if (isInfoEnabled)
153            {
154                getLogger().info(_i18nTranslator.translate(new I18nizableText("plugin.contentio", "PLUGINS_CONTENTIO_CONTENT_EXPORT_LOG_ANALYZE_TABLE_CREATE_END")));
155                getLogger().info(_i18nTranslator.translate(new I18nizableText("plugin.contentio", "PLUGINS_CONTENTIO_CONTENT_EXPORT_LOG_ANALYZE_TABLE_CREATE_MAPPING_BEGIN")));
156            }
157            
158            createMappingTables();
159            
160            if (isInfoEnabled)
161            {
162                getLogger().info(_i18nTranslator.translate(new I18nizableText("plugin.contentio", "PLUGINS_CONTENTIO_CONTENT_EXPORT_LOG_ANALYZE_TABLE_CREATE_MAPPING_END")));
163            }
164            
165            createRichTextDataTable();
166            createContentTable();
167            
168            executeQueries();
169        }
170        finally
171        {
172            ConnectionHelper.cleanup(_connection);
173        }
174        
175        return _tablesInfos;
176    }
177
178    /**
179     * Initialization
180     * @param productVersion The database product version
181     */
182    protected void initialize(String productVersion)
183    {
184        // Get the maximun number of authorized characters for table and column comments
185        if (_databaseType.equals(ConnectionHelper.DATABASE_MYSQL))
186        {
187            String[] parts = productVersion.split("\\.");
188            
189            int majorVersion = Integer.parseInt(parts[0]);
190            int minorVersion = 0;
191            int patchVersion = 0;
192            if (parts.length > 1)
193            {
194                Matcher matcher = _MYSQL_VERSION_NUMBER_EXTRACT.matcher(parts[1]);
195                if (matcher.matches())
196                {
197                    minorVersion = Integer.parseInt(matcher.group(1));
198                }
199            }
200            
201            if (parts.length > 2)
202            {
203                Matcher matcher = _MYSQL_VERSION_NUMBER_EXTRACT.matcher(parts[2]);
204                if (matcher.matches())
205                {
206                    patchVersion = Integer.parseInt(matcher.group(1));
207                }
208            }
209            
210            if (majorVersion > 5 || majorVersion >= 5 && minorVersion > 5 || majorVersion >= 5 && minorVersion >= 5 && patchVersion >= 3)
211            {
212                // Version 5.5.3 or later
213                _commentTableMaxLength = 2048;
214                _commentColumnMaxLength = 1024;
215            }
216            else
217            {
218                // Version before 5.5.3
219                _commentTableMaxLength = 60;
220                _commentColumnMaxLength = 255;
221            }
222        }
223        else
224        {
225            // No max
226            _commentTableMaxLength = 2048;
227            _commentColumnMaxLength = 1024;
228        }
229    }
230    
231    /**
232     * Created all tables informations
233     * @param contents to export
234     * @throws SQLException if a sql error occurred
235     */
236    protected void createTablesInfos(Map<String, String> contents) throws SQLException
237    {
238        for (Entry<String, String> entry : contents.entrySet()) 
239        {
240            String contentTypeId = entry.getKey();
241
242            ContentType contentType = _contentTypeExtensionPoint.getExtension(contentTypeId);
243            if (!contentType.isAbstract())
244            {
245                String tableName = entry.getValue();
246                String comment = _i18nTranslator.translate(contentType.getLabel(), DEFAULT_LANGUAGE_CODE_FOR_COMMENTS) + ": " + _i18nTranslator.translate(contentType.getDescription(), DEFAULT_LANGUAGE_CODE_FOR_COMMENTS);
247                createQueriesForTableCreation(contentType, tableName, null, comment, false);
248            }
249            
250            _sqlTablePrefix = _sqlPrefixConf;
251        }
252    }
253    
254    /**
255     * Create SQL queries to create necessary SQL tables for content type export
256     * @param modelItemContainer the current {@link ModelItemContainer}
257     * @param tableName the table name The SQL table name
258     * @param tableParentName  The SQL parent table name
259     * @param comment the comment
260     * @param isSortTable true if table's rows have to be ordered
261     */
262    protected void createQueriesForTableCreation(ModelItemContainer modelItemContainer, String tableName, String tableParentName, String comment, boolean isSortTable)
263    {
264        ExportTableInfo tableInfo = new ExportTableInfo(tableName);
265        tableInfo.incrementNbColumns();
266        
267        String tableNameNormalized = _normalizeNameComponent.normalizedTableName(_sqlTablePrefix, _mappingPolicy, tableName, _connection);
268        _tablesInfos.put(tableName, tableInfo);
269        
270        StringBuilder currentCreateTableSQLQuery = new StringBuilder();
271        
272        currentCreateTableSQLQuery.append("CREATE TABLE ");
273        currentCreateTableSQLQuery.append(tableNameNormalized);
274        currentCreateTableSQLQuery.append(" (");
275        currentCreateTableSQLQuery.append(_normalizeNameComponent.normalizedColumnName(_mappingPolicy, "id_" + tableName, tableName, _reservedWords, _connection));
276        currentCreateTableSQLQuery.append(" VARCHAR(250)");
277        currentCreateTableSQLQuery.append(createPrimaryKeyQuery());
278        currentCreateTableSQLQuery.append(createCommentQuery(tableName, "id_" + tableName, "Ametys ID"));
279        
280        if (StringUtils.isNotEmpty(tableParentName))
281        {
282            currentCreateTableSQLQuery.append(", ");
283            addColumnParentId(currentCreateTableSQLQuery, tableParentName, tableName);
284        }
285        
286        if (isSortTable)
287        {
288            currentCreateTableSQLQuery.append(", ");
289            addSortColumn(currentCreateTableSQLQuery, tableName);
290        }
291        
292        addColumnForContainer(modelItemContainer, currentCreateTableSQLQuery, tableName, "");
293        
294        if (modelItemContainer instanceof ContentType)
295        {
296            addAdditionalData((ContentType) modelItemContainer, currentCreateTableSQLQuery, tableName);
297        }
298        
299        currentCreateTableSQLQuery.append(") ");
300        currentCreateTableSQLQuery.append(createEngineQuery());
301        currentCreateTableSQLQuery.append(createCommentQuery(tableName, null, comment));
302        
303        tableInfo.addCreateQuery(currentCreateTableSQLQuery.toString());
304    }  
305    
306    /**
307     * Create table for images in rich text
308     */
309    protected void createRichTextDataTable()
310    {
311        String dataTableName = _sqlTablePrefix + ExportManager.RICH_TEXT_DATA_TABLE_NAME;
312        ExportTableInfo tableInfo = new ExportTableInfo(dataTableName);
313        tableInfo.incrementNbColumns(8);
314
315        String dateTableNameNormalized = _normalizeNameComponent.normalizedTableName(_sqlTablePrefix, "FULL", dataTableName, _connection);
316        _tablesInfos.put(dataTableName, tableInfo);
317        
318        StringBuilder currentCreateTableSQLQuery = new StringBuilder();
319
320        currentCreateTableSQLQuery.append("CREATE TABLE ");
321        currentCreateTableSQLQuery.append(dateTableNameNormalized);
322        currentCreateTableSQLQuery.append(" (id_data VARCHAR(250)");
323        currentCreateTableSQLQuery.append(createPrimaryKeyQuery());
324        currentCreateTableSQLQuery.append(createCommentQuery(dataTableName, "id_data", "Data ID"));
325        currentCreateTableSQLQuery.append(", id_content VARCHAR(255)");
326        currentCreateTableSQLQuery.append(createCommentQuery(dataTableName, "id_content", "Parent ID"));
327        
328        currentCreateTableSQLQuery.append(", attribute_name ");
329        currentCreateTableSQLQuery.append(convertTypeToSql("string"));
330        currentCreateTableSQLQuery.append(createCommentQuery(dataTableName, "attribute_name", "Richtext attribute name"));
331        currentCreateTableSQLQuery.append(", data_name ");
332        currentCreateTableSQLQuery.append(convertTypeToSql("string"));
333        currentCreateTableSQLQuery.append(createCommentQuery(dataTableName, "data_name", "Name"));
334        
335        currentCreateTableSQLQuery.append(", data ");
336        currentCreateTableSQLQuery.append(convertTypeToSql("file"));
337        currentCreateTableSQLQuery.append(createCommentQuery(dataTableName, "data", "Data"));
338        currentCreateTableSQLQuery.append(", data_mimetype VARCHAR(255)");
339        currentCreateTableSQLQuery.append(createCommentQuery(dataTableName, "data_mimetype", "Mime type"));
340        currentCreateTableSQLQuery.append(", data_size INT");
341        currentCreateTableSQLQuery.append(createCommentQuery(dataTableName, "data_size", "Size"));
342        currentCreateTableSQLQuery.append(", data_lastmodified ");
343        currentCreateTableSQLQuery.append(convertTypeToSql("datetime"));
344        currentCreateTableSQLQuery.append(createCommentQuery(dataTableName, "data_lastmodified", "Last modification date"));
345        currentCreateTableSQLQuery.append(", ");
346        
347        addSortColumn(currentCreateTableSQLQuery, dataTableName);
348        
349        currentCreateTableSQLQuery.append(") ");
350        currentCreateTableSQLQuery.append(createEngineQuery());
351
352        String comment = "Data table of all rich text";
353        currentCreateTableSQLQuery.append(createCommentQuery(dataTableName, null, comment));
354        
355        tableInfo.addCreateQuery(currentCreateTableSQLQuery.toString());
356    }
357    
358    /**
359     * Create table for content
360     */
361    protected void createContentTable()
362    {
363        String dataTableName = _sqlPrefixConf + ExportManager.CONTENT_TABLE_NAME;
364        ExportTableInfo tableInfo = new ExportTableInfo(dataTableName);
365        tableInfo.incrementNbColumns(2);
366
367        String dataTableNameNormalized = _normalizeNameComponent.normalizedTableName(_sqlTablePrefix, "FULL", dataTableName, _connection);
368        _tablesInfos.put(dataTableName, tableInfo);
369        
370        StringBuilder currentCreateTableSQLQuery = new StringBuilder();
371
372        currentCreateTableSQLQuery.append("CREATE TABLE ");
373        currentCreateTableSQLQuery.append(dataTableNameNormalized);
374        currentCreateTableSQLQuery.append(" (id_content VARCHAR(80)");
375        currentCreateTableSQLQuery.append(createCommentQuery(dataTableName, "id_content", "Content ID"));
376        currentCreateTableSQLQuery.append(", table_name VARCHAR(170)");
377        currentCreateTableSQLQuery.append(createCommentQuery(dataTableName, "table_name", "Table name"));
378        currentCreateTableSQLQuery.append(", CONSTRAINT pk_content PRIMARY KEY (id_content, table_name) ");
379        currentCreateTableSQLQuery.append(") ");
380        currentCreateTableSQLQuery.append(createEngineQuery());
381
382        String comment = "Link table of content with it's own table";
383        currentCreateTableSQLQuery.append(createCommentQuery(dataTableName, null, comment));
384        
385        tableInfo.addCreateQuery(currentCreateTableSQLQuery.toString());
386    }
387    
388    /**
389     * Create the table for the enumerator (key, value)
390     * @param definition the attribute definition
391     * @param tableParentName the table parent name
392     * @param tableName the table name
393     * @param columnName the column name
394     * @param currentCreateTableSQLQuery the current SQL create table query 
395     */
396    protected void createTableForEnumerator(ElementDefinition definition, String tableParentName, String tableName, String columnName, StringBuilder currentCreateTableSQLQuery)
397    {
398        ExportTableInfo tableInfo = new ExportTableInfo(tableName);
399        tableInfo.incrementNbColumns();
400        
401        String tableNameNormalized = _normalizeNameComponent.normalizedTableName(_sqlTablePrefix, _mappingPolicy, tableName, _connection);
402        _tablesInfos.put(tableName, tableInfo);
403        
404        StringBuilder sql = new StringBuilder();
405        
406        sql.append("CREATE TABLE ");
407        sql.append(tableNameNormalized);
408        sql.append(" (key_enum ");
409        sql.append(definition.getType().getId().equals(org.ametys.runtime.model.type.ModelItemTypeConstants.LONG_TYPE_ID) ? "INT" : "VARCHAR(250)");
410        sql.append(createPrimaryKeyQuery());
411        sql.append(createCommentQuery(tableName, "key_enum_" + definition.getName(), "Enumerator key"));
412        
413        addColumn(sql, "value_enum_" + definition.getName(), tableName, null, "string");
414        
415        sql.append(") ");
416        sql.append(createEngineQuery());
417        sql.append(createCommentQuery(tableName, null, "Enumerator table " + definition.getName() + " linked to the table " + tableParentName));
418        
419        tableInfo.addCreateQuery(sql.toString());
420        
421//        String fkName = _normaliseNameComponent.normalizedColumnName(_mappingPolicy, columnName, tableParentName, _connection);
422//        String fkTableName = _normaliseNameComponent.normalizedTableName(_sqlTablePrefix, _mappingPolicy, tableName, _connection);
423//        String fkColumnName = "key_enum";
424//        currentCreateTableSQLQuery.append(createForeignKeyQuery(fkName, fkTableName, fkColumnName));
425        //TODO foreign key ??
426        
427        fillTableForEnumerator(definition, tableName);
428    }
429    
430    /**
431     * Fill values for the enumerator table
432     * @param definition the attribute definition
433     * @param tableName the table name
434     */
435    protected void fillTableForEnumerator(ElementDefinition definition, String tableName)
436    {   
437        try
438        {
439            for (Entry<Object, I18nizableText> entry : ((Map<Object, I18nizableText>) definition.getEnumerator().getTypedEntries()).entrySet()) 
440            {
441                String enumValue = _i18nTranslator.translate(entry.getValue(), DEFAULT_LANGUAGE_CODE_FOR_COMMENTS);
442                String enumKey = entry.getKey().toString();
443
444                StringBuilder sql = new StringBuilder();
445                sql.append("INSERT INTO ");
446                sql.append(_normalizeNameComponent.normalizedTableName(_sqlTablePrefix, _mappingPolicy, tableName, _connection));
447                sql.append(" VALUES ('");
448                sql.append(_normalizeNameComponent.escapeValue(enumKey, _connection));
449                sql.append("', '");
450                sql.append(_normalizeNameComponent.escapeValue(enumValue, _connection));
451                sql.append("')");
452
453                _tablesInfos.get(tableName).addInsertQuery(sql.toString());
454            }
455        }
456        catch (Exception e)
457        {
458            getLogger().warn(e.getMessage(), e);
459        }
460    }
461    
462    /**
463     * Create the two table mapping (for table name and column name)
464     */
465    protected void createMappingTables()
466    {
467        PreparedStatement stmt = null;
468        String mappingTableName = _sqlTablePrefix + ExportManager.MAPPING_TABLE_NAME;
469        String mappingColumnName = _sqlTablePrefix + ExportManager.MAPPING_COLUMN_NAME;
470        
471        ExportTableInfo mappingTableInfo = new ExportTableInfo(mappingTableName);
472        mappingTableInfo.incrementNbColumns(3);
473
474        ExportTableInfo mappingColumnInfo = new ExportTableInfo(mappingColumnName);
475        mappingColumnInfo.incrementNbColumns(3);
476
477        String mappingTableNameNormalized = _normalizeNameComponent.normalizedTableName(_sqlTablePrefix, "FULL", mappingTableName, _connection);
478        String mappingColumnNameNormalized = _normalizeNameComponent.normalizedTableName(_sqlTablePrefix, "FULL", mappingColumnName, _connection);
479        
480        _tablesInfos.put(mappingTableName, mappingTableInfo);
481        _tablesInfos.put(mappingColumnName, mappingColumnInfo);
482        
483        StringBuilder createMappingTableSQLQuery = new StringBuilder();
484
485        createMappingTableSQLQuery.append("CREATE TABLE ");
486        createMappingTableSQLQuery.append(mappingTableNameNormalized);
487        createMappingTableSQLQuery.append(" (id_table INT");
488        createMappingTableSQLQuery.append(createPrimaryKeyQuery());
489        createMappingTableSQLQuery.append(createCommentQuery(mappingTableName, "id_table", "Table name ID"));
490        createMappingTableSQLQuery.append(", real_name VARCHAR(512)");
491        createMappingTableSQLQuery.append(createCommentQuery(mappingTableName, "real_name", "Real name"));
492        createMappingTableSQLQuery.append(", modified_name VARCHAR(512)");
493        createMappingTableSQLQuery.append(createCommentQuery(mappingTableName, "modified_name", "Normalized name"));
494        
495        createMappingTableSQLQuery.append(") ");
496        createMappingTableSQLQuery.append(createEngineQuery());
497
498        String comment = "Mapping table between real name and normalized name.";
499        createMappingTableSQLQuery.append(createCommentQuery(mappingTableName, null, comment));
500        
501        try
502        {
503            _mappingTablesQueries.add(createMappingTableSQLQuery.toString());
504            stmt = _connection.prepareStatement(createMappingTableSQLQuery.toString());
505            stmt.execute();
506        }
507        catch (SQLException e)
508        {
509            getLogger().error(_i18nTranslator.translate(new I18nizableText("plugin.contentio", "PLUGINS_CONTENTIO_CONTENT_EXPORT_LOG_ANALYZE_TABLE_CREATE_MAPPING_TABLE_ERROR")), e);
510        }
511        finally
512        {
513            ConnectionHelper.cleanup(stmt);
514        }
515        
516        StringBuilder createMappingColumnSQLQuery = new StringBuilder();
517
518        createMappingColumnSQLQuery.append("CREATE TABLE ");
519        createMappingColumnSQLQuery.append(mappingColumnNameNormalized);
520        createMappingColumnSQLQuery.append(" (id_table INT");
521        createMappingColumnSQLQuery.append(createCommentQuery(mappingColumnName, "id_table", "Table name id"));
522        createMappingColumnSQLQuery.append(createForeignKeyQuery("id_table", _normalizeNameComponent.normalizedTableName(_sqlTablePrefix, _mappingPolicy, mappingTableName, _connection), "id_table"));
523        createMappingColumnSQLQuery.append(", real_name VARCHAR(512)");
524        createMappingColumnSQLQuery.append(createCommentQuery(mappingColumnName, "real_name", "Real column name"));
525        createMappingColumnSQLQuery.append(", modified_name VARCHAR(512)");
526        createMappingColumnSQLQuery.append(createCommentQuery(mappingColumnName, "modified_name", "Normalized column name"));
527        
528        createMappingColumnSQLQuery.append(") ");
529        createMappingColumnSQLQuery.append(createEngineQuery());
530
531        String commentColum = "Mapping table between real column name and normalized column name.";
532        createMappingColumnSQLQuery.append(createCommentQuery(mappingColumnName, null, commentColum));
533        
534        try
535        {
536            _mappingTablesQueries.add(createMappingColumnSQLQuery.toString());
537            stmt = _connection.prepareStatement(createMappingColumnSQLQuery.toString());
538            stmt.execute();
539        }
540        catch (SQLException e)
541        {
542            getLogger().error(_i18nTranslator.translate(new I18nizableText("plugin.contentio", "PLUGINS_CONTENTIO_CONTENT_EXPORT_LOG_ANALYZE_TABLE_CREATE_MAPPING_COLUMN_ERROR")), e);
543        }
544        finally
545        {
546            ConnectionHelper.cleanup(stmt);
547        }
548        
549        fillMappingTables(mappingTableName, mappingColumnName);
550    }
551    
552    /**
553     * Fill the two mapping table (for table name and column name)
554     * @param mappingTableName the table mapping table name
555     * @param mappingColumnName the column mapping table name
556     */
557    protected void fillMappingTables(String mappingTableName, String mappingColumnName)
558    {
559        HashMap<String, String> mappingTable = (HashMap<String, String>) _normalizeNameComponent.getMappingTableNameFromCache();
560        HashMap<String, HashMap<String, String>> mappingColumn = (HashMap<String, HashMap<String, String>>) _normalizeNameComponent.getMappingTableColumnNameFromCache();
561        
562        PreparedStatement stmtTableName = null;
563        PreparedStatement stmtColumnName = null;
564        try
565        {
566            stmtTableName = getInsertPreparedStatementFromTableName(mappingTableName);
567            stmtColumnName = getInsertPreparedStatementFromTableName(mappingColumnName);
568            int i = 0;
569            for (Entry<String, String> entry : mappingTable.entrySet()) 
570            {
571                String realName = entry.getKey();
572                String modifiedName = entry.getValue();
573                
574                if (getLogger().isDebugEnabled())
575                {
576                    String tableName = _normalizeNameComponent.normalizedTableName(_sqlTablePrefix, _mappingPolicy, mappingTableName, _connection);
577                    getLogger().debug("INSERT INTO {} VALUES ('{}', '{}', '{}')", tableName, i, realName, modifiedName);
578                }
579                
580                stmtTableName.setInt(1, i);
581                stmtTableName.setString(2, realName);
582                stmtTableName.setString(3, modifiedName);
583                
584                stmtTableName.addBatch();
585                
586                if (mappingColumn.containsKey(realName))
587                {
588                    for (Entry<String, String> entryCol : mappingColumn.get(realName).entrySet())
589                    {
590                        String realNameCol = entryCol.getKey();
591                        String modifiedNameCol = entryCol.getValue();
592                        
593                        if (getLogger().isDebugEnabled())
594                        {
595                            String tableName = _normalizeNameComponent.normalizedTableName(_sqlTablePrefix, _mappingPolicy, mappingTableName, _connection);
596                            getLogger().debug("INSERT INTO {} VALUES ('{}', '{}', '{}')", tableName, i, realNameCol, modifiedNameCol);
597                        }
598
599                        stmtColumnName.setInt(1, i);
600                        stmtColumnName.setString(2, realNameCol);
601                        stmtColumnName.setString(3, modifiedNameCol);
602                        
603                        stmtColumnName.addBatch();
604                        
605                    }
606                }
607                i++;
608            }
609            
610            stmtTableName.executeBatch();
611            stmtColumnName.executeBatch();
612        }
613        catch (SQLException e)
614        {
615            getLogger().error(_i18nTranslator.translate(new I18nizableText("plugin.contentio", "PLUGINS_CONTENTIO_CONTENT_EXPORT_LOG_ANALYZE_TABLE_FILL_MAPPING_ERROR")), e);
616        }
617        finally
618        {
619            ConnectionHelper.cleanup(stmtTableName);
620            ConnectionHelper.cleanup(stmtColumnName);
621        }
622    }
623    
624    /**
625     * Add all the column for each attribute in the {@link ModelItemContainer}.
626     * @param modelItemContainer the attribute definitions
627     * @param currentCreateTableSQLQuery the current SQL create table query
628     * @param tableName the table name
629     * @param columnNamePrefix the column namePrefix
630     */
631    protected void addColumnForContainer(ModelItemContainer modelItemContainer, StringBuilder currentCreateTableSQLQuery, String tableName, String columnNamePrefix)
632    {
633        for (ModelItem modelItem : modelItemContainer.getModelItems())
634        {
635            String name = modelItem.getName();
636            String columnName = columnNamePrefix + name;
637            if (modelItem instanceof ElementDefinition)
638            {
639                // simple element
640                ElementDefinition definition = (ElementDefinition) modelItem;
641                if (definition.isMultiple())
642                {
643                    if (_exportNoMultiValuedTable)
644                    {
645                        String comment = _i18nTranslator.translate(definition.getLabel(), DEFAULT_LANGUAGE_CODE_FOR_COMMENTS) + ": " + _i18nTranslator.translate(definition.getDescription(), DEFAULT_LANGUAGE_CODE_FOR_COMMENTS);
646                        addColumn(currentCreateTableSQLQuery, columnName, tableName, comment, "string");
647                    }
648                    else
649                    {
650                        createQueryForMultipleAttribute(definition, tableName, tableName + "_" + columnName); 
651                    }
652                }
653                else
654                {
655                    addColumnForSingleAttribute(definition, currentCreateTableSQLQuery, tableName, columnName);
656                    
657                    if (definition.getEnumerator() != null)
658                    {        
659                        createTableForEnumerator(definition, tableName, tableName + "_" + name, columnName, currentCreateTableSQLQuery);
660                    }
661                }
662            }
663            else if (modelItem instanceof CompositeDefinition)
664            {
665                // composite
666                addColumnForContainer((CompositeDefinition) modelItem, currentCreateTableSQLQuery, tableName, columnName + "_");
667            }
668            else if (modelItem instanceof RepeaterDefinition)
669            {
670                // repeater
671                String commentTable = "Repeater " + _normalizeNameComponent.normalizedColumnName(_mappingPolicy, columnName, tableName, _reservedWords, _connection) + " linked to the table " + _normalizeNameComponent.normalizedTableName(_sqlTablePrefix, _mappingPolicy, tableName, _connection);
672                createQueriesForTableCreation((RepeaterDefinition) modelItem, tableName + "_" + columnName, tableName, commentTable, true);
673            }
674        }
675    }
676    
677    /**
678     * Add column for single attribute
679     * @param definition the attribute definition
680     * @param currentCreateTableSQLQuery the current SQL create table query
681     * @param tableName the table name
682     * @param columnName the column name
683     */
684    protected void addColumnForSingleAttribute(ElementDefinition definition, StringBuilder currentCreateTableSQLQuery, String tableName, String columnName)
685    {
686        String comment = _i18nTranslator.translate(definition.getLabel(), DEFAULT_LANGUAGE_CODE_FOR_COMMENTS) + ": " + _i18nTranslator.translate(definition.getDescription(), DEFAULT_LANGUAGE_CODE_FOR_COMMENTS);
687
688        String type = definition.getType().getId();
689        if (type.equals(ModelItemTypeConstants.USER_ELEMENT_TYPE_ID))
690        {
691            addColumn(currentCreateTableSQLQuery, columnName + "_login", tableName, "User login", "string");
692            addColumn(currentCreateTableSQLQuery, columnName + "_population", tableName, "User population", "string");
693        }
694        else if (type.equals(ModelItemTypeConstants.GEOCODE_ELEMENT_TYPE_ID))
695        {
696            addColumn(currentCreateTableSQLQuery, columnName + "_longitude", tableName, "Longitude of " + columnName, "double");
697            addColumn(currentCreateTableSQLQuery, columnName + "_latitude", tableName, "Latitude type of " + columnName, "double");
698        }
699        else if (type.equals(ModelItemTypeConstants.BINARY_ELEMENT_TYPE_ID) || type.equals(ModelItemTypeConstants.FILE_ELEMENT_TYPE_ID))
700        {
701            String actualColumnName = addColumn(currentCreateTableSQLQuery, columnName, tableName, comment, "string");
702            addColumn(currentCreateTableSQLQuery, actualColumnName + "_data", tableName, "Data of " + columnName, "binary");
703            addColumn(currentCreateTableSQLQuery, actualColumnName + "_mimetype", tableName, "Mime type of " + columnName, "short-string");
704            addColumn(currentCreateTableSQLQuery, actualColumnName + "_size", tableName, "Size of " + columnName, "long");
705            addColumn(currentCreateTableSQLQuery, actualColumnName + "_lastmodified", tableName, "Last modification date of " + columnName, "datetime");
706        }
707        else if (type.equals(ModelItemTypeConstants.MULTILINGUAL_STRING_ELEMENT_TYPE_ID))
708        {
709            for (String lang : _languageManager.getAvailableLanguages().keySet())
710            {
711                addColumn(currentCreateTableSQLQuery, columnName + "_" + lang, tableName, "Value of " + columnName + " for lang " + lang, "string");
712            }
713        }
714        else
715        {
716            addColumn(currentCreateTableSQLQuery, columnName, tableName, comment, type);
717        }
718    }
719    
720    /**
721     * Add a column
722     * @param currentCreateTableSQLQuery the current SQL create table query
723     * @param tableName the table name
724     * @param columnName the desired column name
725     * @param comment the column comment
726     * @param type the column type
727     * @return the actual column name, after normalization of the desired one
728     */
729    protected String addColumn(StringBuilder currentCreateTableSQLQuery, String columnName, String tableName, String comment, String type)
730    {
731        String actualColumnName = _normalizeNameComponent.normalizedColumnName(_mappingPolicy, columnName, tableName, _reservedWords, _connection);
732        
733        currentCreateTableSQLQuery.append(", ").append(actualColumnName);
734        currentCreateTableSQLQuery.append(" ").append(convertTypeToSql(type));
735        
736        if (comment != null)
737        {
738            currentCreateTableSQLQuery.append(createCommentQuery(tableName, columnName, comment));
739        }
740        
741        ExportTableInfo tableInfo = _tablesInfos.get(tableName);
742        tableInfo.incrementNbColumns();
743        
744        return actualColumnName;
745    }
746    
747    /**
748     * Create table for multiple attribute
749     * @param definition the attribute definition
750     * @param tableParentName the name of the table for the attribute's container
751     * @param tableName the table name
752     */
753    protected void createQueryForMultipleAttribute(ElementDefinition definition, String tableParentName, String tableName)
754    {
755        ExportTableInfo tableInfo = new ExportTableInfo(tableName);
756        tableInfo.incrementNbColumns();
757        
758        String normalizeTableName = _normalizeNameComponent.normalizedTableName(_sqlTablePrefix, _mappingPolicy, tableName, _connection);
759        _tablesInfos.put(tableName, tableInfo);
760        
761        String normalizedColumnName = _normalizeNameComponent.normalizedColumnName(_mappingPolicy, COLUMN_PARENT_TABLE_PREFIX + tableParentName, tableName, _reservedWords, _connection);
762        
763        StringBuilder currentCreateTableSQLQuery = new StringBuilder();
764        currentCreateTableSQLQuery.append("CREATE TABLE ");
765        currentCreateTableSQLQuery.append(normalizeTableName);
766        currentCreateTableSQLQuery.append(" (");
767        currentCreateTableSQLQuery.append(normalizedColumnName);
768        currentCreateTableSQLQuery.append(" VARCHAR(245)");
769        currentCreateTableSQLQuery.append(createCommentQuery(tableName, COLUMN_PARENT_TABLE_PREFIX + tableParentName, "Parent ID of the multiple attribute"));
770        
771        String fkName = normalizedColumnName;
772        String fkTableName = _normalizeNameComponent.normalizedTableName(_sqlTablePrefix, _mappingPolicy, tableParentName, _connection);
773        String fkColumnName = _normalizeNameComponent.normalizedColumnName(_mappingPolicy, "id_" + tableParentName, tableParentName, _reservedWords, _connection);
774        currentCreateTableSQLQuery.append(createForeignKeyQuery(fkName, fkTableName, fkColumnName));
775        
776        addColumnForSingleAttribute(definition, currentCreateTableSQLQuery, tableName, definition.getName());
777        
778        currentCreateTableSQLQuery.append(", ");
779        addSortColumn(currentCreateTableSQLQuery, tableName);
780        
781        String primaryKey = "pk_" + _pkIndice;
782        _pkIndice++;
783        
784        currentCreateTableSQLQuery.append(", CONSTRAINT ");
785        currentCreateTableSQLQuery.append(primaryKey);
786        currentCreateTableSQLQuery.append(" PRIMARY KEY (");
787        currentCreateTableSQLQuery.append(normalizedColumnName);
788        currentCreateTableSQLQuery.append(", position)");
789        currentCreateTableSQLQuery.append(") ");
790        currentCreateTableSQLQuery.append(createEngineQuery());
791        
792        String comment = "Multiple attribute " + definition.getName() + " linked to the table" + _normalizeNameComponent.normalizedTableName(_sqlTablePrefix, _mappingPolicy, tableParentName, _connection);
793        currentCreateTableSQLQuery.append(createCommentQuery(tableName, null, comment));
794        
795        tableInfo.addCreateQuery(currentCreateTableSQLQuery.toString());
796    }
797    
798    /**
799     * Add additionnal data for content (title, type, language, creator, creationDate, ....)
800     * @param contentType the model
801     * @param currentCreateTableSQLQuery the current SQL create table query
802     * @param tableName the table name
803     */
804    protected void addAdditionalData(ContentType contentType, StringBuilder currentCreateTableSQLQuery, String tableName)
805    {
806        addColumn(currentCreateTableSQLQuery, "content_title", tableName, "Content title", "string");
807        addColumn(currentCreateTableSQLQuery, "content_type", tableName, "Content type", "string");
808        addColumn(currentCreateTableSQLQuery, "content_language", tableName, "Content lang", "string");
809        addColumn(currentCreateTableSQLQuery, "content_creator", tableName, "Content author", "string");
810        addColumn(currentCreateTableSQLQuery, "content_creationDate", tableName, "Content creation date", "datetime");
811        addColumn(currentCreateTableSQLQuery, "content_lastContributor", tableName, "Content last contributor", "string");
812        addColumn(currentCreateTableSQLQuery, "content_lastModificationDate", tableName, "Content last modification date", "datetime");
813        addColumn(currentCreateTableSQLQuery, "content_lastValidationDate", tableName, "Content last validation date", "datetime");
814        addColumn(currentCreateTableSQLQuery, "content_lastMajorValidationDate", tableName, "Content last major validation date", "datetime");
815    }
816    
817    /**
818     * Add a column to sort
819     * @param currentCreateTableSQLQuery the current SQL create table query
820     * @param tableName the table name
821     */
822    protected void addSortColumn(StringBuilder currentCreateTableSQLQuery, String tableName)
823    {
824        currentCreateTableSQLQuery.append("position INT");
825        currentCreateTableSQLQuery.append(createCommentQuery(tableName, "position", "Order of the row"));
826        
827        ExportTableInfo tableInfo = _tablesInfos.get(tableName);
828        tableInfo.incrementNbColumns();
829    }
830    
831    /**
832     * Add an id column which refer to another table
833     * @param currentCreateTableSQLQuery the current SQL create table query
834     * @param tableParentName the table parent name
835     * @param tableName the table name
836     */
837    protected void addColumnParentId(StringBuilder currentCreateTableSQLQuery, String tableParentName, String tableName)
838    {
839        currentCreateTableSQLQuery.append(_normalizeNameComponent.normalizedColumnName(_mappingPolicy, COLUMN_PARENT_TABLE_PREFIX + tableParentName, tableName, _reservedWords, _connection));
840        currentCreateTableSQLQuery.append(" VARCHAR(250)"); //TODO NOT NULL
841        currentCreateTableSQLQuery.append(createCommentQuery(tableName, COLUMN_PARENT_TABLE_PREFIX + tableParentName, "Parent table ID " + tableParentName));
842
843        String fkName = _normalizeNameComponent.normalizedColumnName(_mappingPolicy, COLUMN_PARENT_TABLE_PREFIX + tableParentName, tableName, _reservedWords, _connection);
844        String fkTableName = _normalizeNameComponent.normalizedTableName(_sqlTablePrefix, _mappingPolicy, tableParentName, _connection);
845        String fkColumnName = _normalizeNameComponent.normalizedColumnName(_mappingPolicy, "id_" + tableParentName, tableParentName, _reservedWords, _connection);
846        currentCreateTableSQLQuery.append(createForeignKeyQuery(fkName, fkTableName, fkColumnName));
847        
848        ExportTableInfo tableInfo = _tablesInfos.get(tableName);
849        tableInfo.incrementNbColumns();
850    }
851    
852    /**
853     * Create the query to add comment to a table or a column
854     * @param table the table name
855     * @param column the column name
856     * @param comments the comments
857     * @return the query to add comment to a table or a column
858     */
859    protected String createCommentQuery(String table, String column, String comments)
860    {
861        String normalizedComment = _normalizeNameComponent.normalizedComment(comments, StringUtils.isEmpty(column) ? _commentTableMaxLength : _commentColumnMaxLength, _connection);
862        
863        String commentSql = "";
864        if (_databaseType.equals(ConnectionHelper.DATABASE_MYSQL))
865        {
866            commentSql = " COMMENT '" + normalizedComment + "'";
867        }
868        else if (_databaseType.equals(ConnectionHelper.DATABASE_ORACLE)) //TODO
869        {
870            /*if (StringUtils.isNotEmpty(table)) 
871            {
872                if (StringUtils.isEmpty(column)) 
873                {
874//                    _tablesInfos.get(table).addCommentQuery("COMMENT ON TABLE " + _normaliseNameComponent.normalizedTableName(_sqlTablePrefix, _mappingPolicy, table, _connection) + " IS '" + normalizedComment + "'");
875                }
876                else
877                {
878//                    _tablesInfos.get(table).addCommentQuery("COMMENT ON COLUMN " + _normaliseNameComponent.normalizedTableName(_sqlTablePrefix, _mappingPolicy, table, _connection) + "." + _normaliseNameComponent.normalizedColumnName(_mappingPolicy, column, table, _connection) + " IS '" + normalizedComment + "'");
879                }
880            }*/
881        }
882        
883        return commentSql;
884    }
885    
886    /**
887     * Create the query to add foreign key
888     * @param fkName the foreign key name
889     * @param tableName the table name 
890     * @param columnName the column name 
891     * @return the query to add comment to a table or a column
892     */
893    protected String createForeignKeyQuery(String fkName, String tableName, String columnName)
894    {
895        StringBuilder foreignKeySQLQuery = new StringBuilder();
896        foreignKeySQLQuery.append(", CONSTRAINT fk_");
897        foreignKeySQLQuery.append(_fkIndice);
898        _fkIndice++;
899
900        foreignKeySQLQuery.append(" FOREIGN KEY (");
901        foreignKeySQLQuery.append(fkName);
902        foreignKeySQLQuery.append(")");
903        foreignKeySQLQuery.append(" REFERENCES ");
904        foreignKeySQLQuery.append(tableName);
905        foreignKeySQLQuery.append(" (");
906        foreignKeySQLQuery.append(columnName);
907        foreignKeySQLQuery.append(")");
908        
909        return foreignKeySQLQuery.toString();
910    }
911    
912    /**
913     * Create the query to add engine
914     * @return the query to add engine
915     */
916    protected String createEngineQuery()
917    {
918        if (_databaseType.equals(ConnectionHelper.DATABASE_MYSQL))
919        {
920            return "ENGINE=" + MYSQL_CONTENT_EXPORT_ENGINE + " ROW_FORMAT=COMPRESSED DEFAULT CHARACTER SET " + MYSQL_CONTENT_EXPORT_CHARSET + " ";
921        }
922        else if (_databaseType.equals(ConnectionHelper.DATABASE_ORACLE))
923        {
924            return "STORAGE (INITIAL 8K NEXT 8K)";
925        }
926
927        return "";
928    }
929    
930    /**
931     * Create the query to add primary key
932     * @return the query to add primary key
933     */
934    protected String createPrimaryKeyQuery()
935    {
936        return " PRIMARY KEY NOT NULL";
937    }
938
939    /**
940     * Return the sql type corresponding to the attribute and the database type
941     * @param type the attribute's type
942     * @return the sql type
943     */
944    protected String convertTypeToSql(String type)
945    {
946        String sqlType = "";
947        
948        if (_databaseType.equals(ConnectionHelper.DATABASE_MYSQL))
949        {
950            sqlType = _mapping.get("mysql").get(type);
951        }
952        else if (_databaseType.equals(ConnectionHelper.DATABASE_ORACLE))
953        {
954            sqlType = _mapping.get("oracle").get(type);
955        }
956        
957        return sqlType;
958    }
959    
960    /**
961     * Execute SQL queries
962     * @throws SQLException if a sql error occurred
963     * @throws IOException if an IO error occurred
964     */
965    protected void executeQueries() throws SQLException, IOException
966    {
967        int nbTotalTable = _getNbTable();
968
969        boolean isInfoEnabled = getLogger().isInfoEnabled();
970        if (isInfoEnabled)
971        {
972            List<String> i18nParams = new ArrayList<>();
973            i18nParams.add(String.valueOf(nbTotalTable));
974            getLogger().info(_i18nTranslator.translate(new I18nizableText("plugin.contentio", "PLUGINS_CONTENTIO_CONTENT_EXPORT_LOG_ANALYZE_TABLE_CREATE_BEGINNING", i18nParams)));
975        }
976
977        int nbTableCreated = 0;
978        int limitPourcentReport = 10;
979        try
980        {
981            for (Entry<String, ExportTableInfo> entry : _tablesInfos.entrySet()) 
982            {
983                ExportTableInfo tableInfo = entry.getValue();
984                
985                List<String> queries = tableInfo.getCreateQueries();
986                _executeQueries(queries);
987                nbTableCreated += queries.size();
988                
989                int pourcent = nbTableCreated * 100 / nbTotalTable;
990                if (pourcent >= limitPourcentReport)
991                {
992                    if (isInfoEnabled)
993                    {
994                        List<String> i18nParams = new ArrayList<>();
995                        i18nParams.add(String.valueOf(limitPourcentReport));
996                        getLogger().info(_i18nTranslator.translate(new I18nizableText("plugin.contentio", "PLUGINS_CONTENTIO_CONTENT_EXPORT_LOG_ANALYZE_TABLE_CREATE_ADVANCE", i18nParams)));
997                    }
998                    limitPourcentReport += 10;
999                }
1000                
1001                _executeQueries(tableInfo.getCommentQueries());
1002                _executeQueries(tableInfo.getInsertQueries());
1003            }
1004        }
1005        catch (IOException e)
1006        {
1007            getLogger().error(_i18nTranslator.translate(new I18nizableText("plugin.contentio", "PLUGINS_CONTENTIO_CONTENT_EXPORT_LOG_ANALYZE_TABLE_GET_FILE_ERROR")), e);
1008        }
1009        finally
1010        {
1011            if (isInfoEnabled)
1012            {
1013                List<String> i18nParams = new ArrayList<>();
1014                i18nParams.add(String.valueOf(nbTableCreated));
1015                getLogger().info(_i18nTranslator.translate(new I18nizableText("plugin.contentio", "PLUGINS_CONTENTIO_CONTENT_EXPORT_LOG_ANALYZE_TABLE_CREATE_FINISH", i18nParams)));
1016            }
1017        }
1018    }
1019    
1020    private int _getNbTable()
1021    {
1022        int nbTable = 0;
1023        for (Entry<String, ExportTableInfo> entry : _tablesInfos.entrySet())
1024        {
1025            ExportTableInfo tableInfo = entry.getValue();
1026            
1027            List<String> listQuery = tableInfo.getCreateQueries();
1028            nbTable += listQuery.size();
1029        }
1030        
1031        return nbTable;
1032    }
1033    
1034    /**
1035     * Execute query list
1036     * @param listQuery the list of query to execute
1037     * @throws SQLException if a sql error occurred
1038     * @throws IOException if an IO error occurred
1039     */
1040    protected void _executeQueries(List<String> listQuery) throws SQLException, IOException
1041    {
1042        for (String query : listQuery)
1043        {
1044            getLogger().debug(query);
1045
1046            PreparedStatement stmt = null;
1047            try
1048            {
1049                stmt = _connection.prepareStatement(query);
1050                stmt.execute();
1051            }
1052            catch (SQLException e)
1053            {
1054                throw new SQLException("The SQL query failed : " + query, e);
1055            }
1056            finally
1057            {
1058                // Close the connection resources
1059                ConnectionHelper.cleanup(stmt);
1060            }
1061        }
1062    }
1063    
1064    /**
1065     * Prepare INSERT statement
1066     * @param tableName the table name
1067     * @return the INSERT preparedStatement
1068     * @throws SQLException if a sql error occurred
1069     */
1070    protected PreparedStatement getInsertPreparedStatementFromTableName(String tableName) throws SQLException
1071    {
1072        StringBuilder sql = new StringBuilder();
1073        sql.append("INSERT INTO ");
1074        sql.append(_normalizeNameComponent.normalizedTableName(_sqlTablePrefix, _mappingPolicy, tableName, _connection));
1075        sql.append(" VALUES ( ?");
1076        
1077        ExportTableInfo tableInfo = _tablesInfos.get(tableName);
1078
1079        for (int i = 1; i < tableInfo.getNbColumns(); i++)
1080        {
1081            sql.append(", ?");
1082        }
1083        sql.append(")");
1084        
1085        PreparedStatement stmt = _connection.prepareStatement(sql.toString());
1086        return stmt;
1087    }
1088}