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