001/*
002 *  Copyright 2015 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;
017
018import java.io.IOException;
019import java.sql.SQLException;
020import java.util.HashMap;
021import java.util.Map;
022
023import org.apache.avalon.framework.component.Component;
024import org.apache.avalon.framework.configuration.Configurable;
025import org.apache.avalon.framework.configuration.Configuration;
026import org.apache.avalon.framework.configuration.ConfigurationException;
027import org.apache.avalon.framework.context.ContextException;
028import org.apache.avalon.framework.service.ServiceException;
029import org.apache.avalon.framework.service.ServiceManager;
030import org.apache.avalon.framework.service.Serviceable;
031import org.apache.commons.lang.StringUtils;
032
033import org.ametys.cms.contenttype.ContentTypeExtensionPoint;
034import org.ametys.core.util.I18nUtils;
035import org.ametys.plugins.contentio.export.component.CreateSqlTableComponent;
036import org.ametys.plugins.contentio.export.component.DeleteSqlTableComponent;
037import org.ametys.plugins.contentio.export.component.FillSqlTableComponent;
038import org.ametys.plugins.contentio.export.object.ExportConfiguration;
039import org.ametys.plugins.contentio.export.object.ExportTableInfo;
040import org.ametys.plugins.repository.AmetysRepositoryException;
041import org.ametys.runtime.i18n.I18nizableText;
042import org.ametys.runtime.plugin.component.AbstractLogEnabled;
043
044/**
045 * This component handles content export in database
046 */
047public class ExportManager extends AbstractLogEnabled implements Configurable, Component, Serviceable
048{
049    /** The component role */
050    public static final String ROLE = ExportManager.class.getName();
051
052    /** The default prefix */
053    public static final String DEFAULT_TABLE_PREFIX = "AmetysExport";
054    
055    /** Connection pool name */
056    public static final String CONTENT_EXPORT_POOL_NAME = "content.export.jdbc.pool";
057    
058    /** The name of the table for datas from rich text */
059    public static final String RICH_TEXT_DATA_TABLE_NAME = "Ametys_RichTextImages";
060    
061    /** The name of the table for mapping table name */
062    public static final String MAPPING_TABLE_NAME = "Ametys_tableMapping";
063    
064    /** The name of the table for mapping column name */
065    public static final String MAPPING_COLUMN_NAME = "Ametys_columnMapping";
066    
067    /** The name of the table for mapping column name */
068    public static final String CONTENT_TABLE_NAME = "Ametys_AllContents ";
069    
070    /** The default separator */
071    public static final String DEFAULT_SEPARATOR = ", ";
072    
073    /** Content type extension point. */
074    protected ContentTypeExtensionPoint _contentTypeExtensionPoint;
075  
076    /** The i18n translator. */
077    protected I18nUtils _i18nTranslator;
078    
079    /** The delete sql table component. */
080    protected DeleteSqlTableComponent _deleteSqlTableComponent;
081    
082    /** The create sql table component. */
083    protected CreateSqlTableComponent _createSqlTableComponent;
084    
085    /** The fill sql table component. */
086    protected FillSqlTableComponent _fillSqlTableComponent;
087    
088    private ExportConfiguration _exportConfiguration;
089    
090    @Override
091    public void service(ServiceManager sManager) throws ServiceException
092    {
093        _contentTypeExtensionPoint = (ContentTypeExtensionPoint) sManager.lookup(ContentTypeExtensionPoint.ROLE);
094        _i18nTranslator = (I18nUtils) sManager.lookup(I18nUtils.ROLE);
095        _deleteSqlTableComponent = (DeleteSqlTableComponent) sManager.lookup(DeleteSqlTableComponent.ROLE);
096        _createSqlTableComponent = (CreateSqlTableComponent) sManager.lookup(CreateSqlTableComponent.ROLE);
097        _fillSqlTableComponent = (FillSqlTableComponent) sManager.lookup(FillSqlTableComponent.ROLE);
098    }
099    
100    @Override
101    public void configure(Configuration configuration) throws ConfigurationException
102    {
103        _exportConfiguration = new ExportConfiguration();
104        
105        // Set the table prefix
106        String tablePrefix = configuration.getAttribute("prefix", DEFAULT_TABLE_PREFIX);
107        if (StringUtils.isNotEmpty(tablePrefix) && !StringUtils.endsWith(tablePrefix, "_"))
108        {
109            tablePrefix += "_";
110        }
111        _exportConfiguration.setTablePrefix(tablePrefix);
112        
113        String exportOnlyValidatedContentAsString = configuration.getAttribute("exportOnlyValidatedContent", "false");
114        _exportConfiguration.setExportOnlyValidatedContent("true".equals(exportOnlyValidatedContentAsString));
115        
116        String exportNoMultiValuedTableAsString = configuration.getAttribute("exportNoMultiValuedTable", "false");
117        _exportConfiguration.setExportNoMultiValuedTable("true".equals(exportNoMultiValuedTableAsString));
118        
119        String separator = configuration.getAttribute("separator", DEFAULT_SEPARATOR);
120        _exportConfiguration.setSeparator(separator);
121        
122        // Set the content type to export
123        _configureContentTypeToExport(configuration, tablePrefix);
124        
125        // Set the sql mapping
126        _configureSQLMapping(configuration);
127        
128        // Set the reserved words
129        _configureReservedWords(configuration);
130        
131        // Set the mapping policy
132        String mappingPolicy = configuration.getChild("mapping-policy").getAttribute("value", "FULL");
133        _exportConfiguration.setMappingPolicy(mappingPolicy);
134    }
135
136    /**
137     * Configure the content type to export
138     * @param configuration the configuration
139     * @param tablePrefix the table prefix
140     * @throws ConfigurationException if an error occurred
141     */
142    protected void _configureContentTypeToExport(Configuration configuration, String tablePrefix) throws ConfigurationException
143    {
144        Map<String, String> contentTypesToExport = new HashMap<>();
145        for (Configuration contentConf : configuration.getChild("contents").getChildren("content"))
146        {
147            String cTypeId = contentConf.getAttribute("id");
148            String name = contentConf.getAttribute("name", null);
149            if (StringUtils.isNotBlank(name))
150            {
151                contentTypesToExport.put(cTypeId, tablePrefix + name);
152            }
153            else
154            {
155                String defaultTableName = cTypeId.substring(cTypeId.lastIndexOf(".") + 1);
156                contentTypesToExport.put(cTypeId, tablePrefix + defaultTableName);
157            }
158        }
159        
160        if (contentTypesToExport.isEmpty())
161        {
162            for (String cTypeId : _contentTypeExtensionPoint.getExtensionsIds())
163            {
164                String defaultTableName = cTypeId.substring(cTypeId.lastIndexOf(".") + 1);
165                contentTypesToExport.put(cTypeId, tablePrefix + defaultTableName);
166            }
167        }
168        
169        _exportConfiguration.setContentTypesToExport(contentTypesToExport);
170    }
171
172    /**
173     * Configure the reserved words
174     * @param configuration the configuration
175     * @throws ConfigurationException if an error occurred
176     */
177    protected void _configureReservedWords(Configuration configuration) throws ConfigurationException
178    {
179        boolean override = "true".equals(configuration.getChild("reserved-words").getAttribute("override", "false"));
180        
181        Map<String, Map<String, String>> reservedWords = new HashMap<>();
182
183        Configuration mysqlReservedWords = configuration.getChild("reserved-words").getChild("mysql");
184        Map<String, String> mysqlReservedWordsMap = new HashMap<>();
185        if (!override)
186        {
187            mysqlReservedWordsMap = ReservedWordsUtils.RESERVED_WORDS_MYSQL;
188        }
189        for (Configuration wordConf : mysqlReservedWords.getChildren("word"))
190        {
191            mysqlReservedWordsMap.put(wordConf.getAttribute("name"), wordConf.getAttribute("alias"));
192        }
193        if (mysqlReservedWordsMap.isEmpty())
194        {
195            mysqlReservedWordsMap = ReservedWordsUtils.RESERVED_WORDS_MYSQL;
196        }
197        reservedWords.put(ReservedWordsUtils.MYSQL_KEY, mysqlReservedWordsMap);
198        
199        Configuration oracleReservedWords = configuration.getChild("reserved-words").getChild("oracle");
200        Map<String, String> oracleReservedWordsMap = new HashMap<>();
201        if (!override)
202        {
203            oracleReservedWordsMap = ReservedWordsUtils.RESERVED_WORDS_ORACLE;
204        }
205        for (Configuration wordConf : oracleReservedWords.getChildren("word"))
206        {
207            oracleReservedWordsMap.put(wordConf.getAttribute("name"), wordConf.getAttribute("alias"));
208        }
209        if (oracleReservedWordsMap.isEmpty())
210        {
211            oracleReservedWordsMap = ReservedWordsUtils.RESERVED_WORDS_ORACLE;
212        }
213        reservedWords.put(ReservedWordsUtils.ORACLE_KEY, oracleReservedWordsMap);
214        
215        _exportConfiguration.setReservedWords(reservedWords);
216    }
217
218    /**
219     * Configure the sql mapping
220     * @param configuration the configuration
221     */
222    protected void _configureSQLMapping(Configuration configuration)
223    {
224        Map<String, Map<String, String>> mappingSql = new HashMap<>();
225        
226        Map<String, String> mappingSqlMetadata = new HashMap<>();
227        mappingSqlMetadata.put("string", configuration.getChild("sql").getChild("mysql").getChild("string").getValue("TEXT"));
228        mappingSqlMetadata.put("long", configuration.getChild("sql").getChild("mysql").getChild("long").getValue("INT"));
229        mappingSqlMetadata.put("boolean", configuration.getChild("sql").getChild("mysql").getChild("boolean").getValue("TINYINT"));
230        mappingSqlMetadata.put("date", configuration.getChild("sql").getChild("mysql").getChild("date").getValue("DATE"));
231        mappingSqlMetadata.put("datetime", configuration.getChild("sql").getChild("mysql").getChild("datetime").getValue("DATETIME"));
232        mappingSqlMetadata.put("double", configuration.getChild("sql").getChild("mysql").getChild("double").getValue("DOUBLE"));
233        mappingSqlMetadata.put("richtext", configuration.getChild("sql").getChild("mysql").getChild("richtext").getValue("MEDIUMTEXT"));
234        mappingSqlMetadata.put("content", configuration.getChild("sql").getChild("mysql").getChild("content").getValue("VARCHAR(512)"));
235        mappingSqlMetadata.put("file", configuration.getChild("sql").getChild("mysql").getChild("file").getValue("LONGBLOB"));
236        mappingSqlMetadata.put("binary", configuration.getChild("sql").getChild("mysql").getChild("binary").getValue("LONGBLOB"));
237        mappingSql.put("mysql", mappingSqlMetadata);
238        
239        Map<String, String> mappingOracleMetadata = new HashMap<>();
240        mappingOracleMetadata.put("string", configuration.getChild("sql").getChild("oracle").getChild("string").getValue("VARCHAR(4000)"));
241        mappingOracleMetadata.put("long", configuration.getChild("sql").getChild("oracle").getChild("long").getValue("NUMBER"));
242        mappingOracleMetadata.put("boolean", configuration.getChild("sql").getChild("oracle").getChild("boolean").getValue("CHAR(1)"));
243        mappingOracleMetadata.put("date", configuration.getChild("sql").getChild("oracle").getChild("date").getValue("DATE"));
244        mappingOracleMetadata.put("datetime", configuration.getChild("sql").getChild("oracle").getChild("datetime").getValue("DATE"));
245        mappingOracleMetadata.put("double", configuration.getChild("sql").getChild("oracle").getChild("double").getValue("NUMBER"));
246        mappingOracleMetadata.put("richtext", configuration.getChild("sql").getChild("oracle").getChild("richtext").getValue("VARCHAR(4000)"));
247        mappingOracleMetadata.put("content", configuration.getChild("sql").getChild("oracle").getChild("content").getValue("VARCHAR(4000)"));
248        mappingOracleMetadata.put("file", configuration.getChild("sql").getChild("oracle").getChild("file").getValue("BLOB"));
249        mappingOracleMetadata.put("binary", configuration.getChild("sql").getChild("oracle").getChild("binary").getValue("BLOB"));
250        mappingSql.put("oracle", mappingOracleMetadata);
251        
252        _exportConfiguration.setMappingSql(mappingSql);
253    }
254
255    /**
256     * Export the configured content types to the CONTENT_EXPORT_POOL_NAME db pool
257     * @throws SQLException If a sql error occurred
258     * @throws ContextException  if a context error occurred
259     * @throws AmetysRepositoryException if a ametys repository error occurred
260     * @throws IOException if an IO error occurred
261     */
262    public void export() throws SQLException, AmetysRepositoryException, ContextException, IOException
263    {
264        boolean isInfoEnabled = getLogger().isInfoEnabled();
265        if (isInfoEnabled)
266        {
267            getLogger().info(_i18nTranslator.translate(new I18nizableText("plugin.contentio", "PLUGINS_CONTENTIO_CONTENT_EXPORT_LOG_BEGIN")));
268            getLogger().info(_i18nTranslator.translate(new I18nizableText("plugin.contentio", "PLUGINS_CONTENTIO_CONTENT_EXPORT_LOG_INIT_BEGIN")));
269        }
270        
271        // Initialization
272        initialize();
273        
274        if (isInfoEnabled)
275        {
276            getLogger().info(_i18nTranslator.translate(new I18nizableText("plugin.contentio", "PLUGINS_CONTENTIO_CONTENT_EXPORT_LOG_INIT_END")));
277            getLogger().info(_i18nTranslator.translate(new I18nizableText("plugin.contentio", "PLUGINS_CONTENTIO_CONTENT_EXPORT_LOG_DELETE_BEGIN")));
278        }
279        
280        // First delete all tables
281        _deleteSqlTableComponent.deleteExistingSqlTables(_exportConfiguration);
282
283        if (isInfoEnabled)
284        {
285            getLogger().info(_i18nTranslator.translate(new I18nizableText("plugin.contentio", "PLUGINS_CONTENTIO_CONTENT_EXPORT_LOG_CREATE_BEGIN")));
286        }
287        
288        Map<String, ExportTableInfo> tableInfo = new HashMap<>();
289        
290        // Then create all tables
291        tableInfo = _createSqlTableComponent.createSqlTables(_exportConfiguration);
292        
293        if (isInfoEnabled)
294        {
295            getLogger().info(_i18nTranslator.translate(new I18nizableText("plugin.contentio", "PLUGINS_CONTENTIO_CONTENT_EXPORT_LOG_FILL_BEGIN")));
296        }
297        
298        // Finally insert values
299        _fillSqlTableComponent.fillTable(_exportConfiguration, tableInfo);
300    }
301    
302    /**
303     * Initialization
304     */
305    protected void initialize()
306    {
307        Map<String, String> contents = _exportConfiguration.getContentTypesToExport();
308
309        if (contents.isEmpty())
310        {
311            String prefix = _exportConfiguration.getTablePrefix();
312            for (String contentTypeId : _contentTypeExtensionPoint.getExtensionsIds())
313            {
314                String defaultTableName = prefix + contentTypeId.substring(contentTypeId.lastIndexOf(".") + 1);
315                contents.put(contentTypeId, defaultTableName);
316            }
317            
318            _exportConfiguration.setContentTypesToExport(contents);
319        }
320    }
321    
322    /**
323     * Get export configuration
324     * @return the export configuration
325     */
326    public ExportConfiguration getExportConfiguration()
327    {
328        return _exportConfiguration;
329    }
330    
331    /**
332     * Set export configuration
333     * @param exportConfiguration the export configuration
334     */
335    public void setExportConfiguration(ExportConfiguration exportConfiguration)
336    {
337        this._exportConfiguration = exportConfiguration;
338    }
339}