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.sql.Connection;
019import java.util.HashMap;
020import java.util.Map;
021
022import org.apache.avalon.framework.component.Component;
023import org.apache.avalon.framework.context.Context;
024import org.apache.avalon.framework.context.ContextException;
025import org.apache.avalon.framework.context.Contextualizable;
026import org.apache.cocoon.components.ContextHelper;
027import org.apache.cocoon.environment.Request;
028import org.apache.commons.lang.StringUtils;
029import org.apache.commons.lang.WordUtils;
030
031import org.ametys.core.datasource.ConnectionHelper;
032import org.ametys.runtime.plugin.component.AbstractLogEnabled;
033
034/**
035 * Normalize name component
036 */
037public class NormalizeNameComponent extends AbstractLogEnabled implements Component, Contextualizable
038{
039    /** The component role */
040    public static final String ROLE = NormalizeNameComponent.class.getName();
041    
042    /** The avalon context. */
043    protected Context _context;
044        
045    public void contextualize(Context context) throws ContextException
046    {
047        _context = context;
048    }
049    
050    /**
051     * Get the normalized table name
052     * @param prefix the table name prefix
053     * @param mappingPolicy the mapping policy
054     * @param initialTableName The initial table name
055     * @param connection the connection
056     * @return normalized table name
057     */
058    protected String normalizedTableName (String prefix, String mappingPolicy, String initialTableName, Connection connection)
059    {
060        Map<String, String> mappingTableName = getMappingTableNameFromCache();
061        if (mappingTableName.containsKey(initialTableName))
062        {
063            return mappingTableName.get(initialTableName);
064        }
065        else
066        {
067            String initialTableNameNoPrefix = initialTableName.substring(prefix.length());
068            String normalizedTableName = initialTableNameNoPrefix.replace("-", "_");
069            StringBuilder changedInitialName = new StringBuilder(); 
070            int i = 0;
071            
072            for (String name : normalizedTableName.split("_"))
073            {
074                if (i != 0)
075                {
076                    changedInitialName.append("_");
077                }
078                changedInitialName.append(normalizeNameFromPolicy(mappingPolicy, StringUtils.capitalize(name)));
079                i++;
080            }
081            
082            normalizedTableName = prefix + changedInitialName.toString();
083
084            int maxLength = Integer.MAX_VALUE;
085            
086            String datatype = ConnectionHelper.getDatabaseType(connection);
087            if (datatype.equals(ConnectionHelper.DATABASE_MYSQL))
088            {
089                maxLength = 64 /* mysql max */;
090            }
091            else if (datatype.equals(ConnectionHelper.DATABASE_ORACLE))
092            {
093                maxLength = 30 /* oracle max */;
094            }
095            
096            if (normalizedTableName.length() >= maxLength)
097            {
098                final int hashSize = 6;
099                normalizedTableName = normalizedTableName.substring(0, maxLength - hashSize) + String.valueOf((int) Math.abs(normalizedTableName.hashCode() % Math.pow(10, hashSize)));
100            }
101            
102            if (mappingTableName.containsValue(normalizedTableName))
103            {
104                normalizedTableName = StringUtils.substring(normalizedTableName, 0, normalizedTableName.length() - 2) + "_" + (_getCountSameValue(normalizedTableName, mappingTableName, 1));
105            }
106            
107            mappingTableName.put(initialTableName, normalizedTableName);
108            
109            return normalizedTableName;
110        }
111    }
112    
113    /**
114     * Get the normalized column name
115     * @param mappingPolicy the mappingPolicy
116     * @param initialColumnName The initial column name
117     * @param initialTableName The initial table name
118     * @param reservedWords the map of reserved words
119     * @param connection the connection
120     * @return normalized column name
121     */
122    protected String normalizedColumnName (String mappingPolicy, String initialColumnName, String initialTableName, Map<String, Map<String, String>> reservedWords, Connection connection)
123    {
124        String datatype = ConnectionHelper.getDatabaseType(connection);
125        String columnName = initialColumnName;
126        if (datatype.equals(ConnectionHelper.DATABASE_MYSQL))
127        {
128            Map<String, String> mysqlReservedWords = reservedWords.get("mysql");
129            if (mysqlReservedWords.containsKey(StringUtils.upperCase(initialColumnName)))
130            {
131                columnName = StringUtils.lowerCase(mysqlReservedWords.get(StringUtils.upperCase(initialColumnName)));
132            }
133        }
134        else if (datatype.equals(ConnectionHelper.DATABASE_ORACLE))
135        {
136            Map<String, String> oracleReservedWords = reservedWords.get("oracle");
137            if (oracleReservedWords.containsKey(StringUtils.upperCase(initialColumnName)))
138            {
139                columnName = StringUtils.lowerCase(oracleReservedWords.get(StringUtils.upperCase(initialColumnName)));
140            }
141        }
142        
143        Map<String, HashMap<String, String>> mappingTableColumnName = getMappingTableColumnNameFromCache();
144        HashMap<String, String> mappingColumnName = new HashMap<>();
145        if (!mappingTableColumnName.containsKey(initialTableName))
146        {
147            mappingTableColumnName.put(initialTableName, mappingColumnName);
148        }
149        
150        mappingColumnName = mappingTableColumnName.get(initialTableName);
151        if (mappingColumnName.containsKey(columnName))
152        {
153            return mappingColumnName.get(columnName);
154        }
155        else
156        {
157            String normalizedColumnName = columnName.replace("-", "_");
158            StringBuilder changedInitialName = new StringBuilder(); 
159            int i = 0;
160            for (String name : normalizedColumnName.split("_"))
161            {
162                if (i != 0)
163                {
164                    changedInitialName.append("_");
165                }
166                
167                changedInitialName.append(normalizeNameFromPolicy(mappingPolicy, StringUtils.capitalize(name)));
168                i++;
169            }
170            
171            normalizedColumnName = changedInitialName.toString();
172            
173            int maxLength = Integer.MAX_VALUE;
174            
175            if (datatype.equals(ConnectionHelper.DATABASE_MYSQL))
176            {
177                maxLength = 64 /* mysql max */;
178            }
179            else if (datatype.equals(ConnectionHelper.DATABASE_ORACLE))
180            {
181                maxLength = 30 /* oracle max */;
182            }
183            
184            if (normalizedColumnName.length() > maxLength)
185            {
186                final int hashSize = 9;
187                normalizedColumnName = normalizedColumnName.substring(0, maxLength - hashSize) + String.valueOf((int) Math.abs(normalizedColumnName.hashCode() % Math.pow(10, hashSize)));
188            }
189            
190            if (mappingColumnName.containsValue(normalizedColumnName))
191            {
192                normalizedColumnName = StringUtils.substring(normalizedColumnName, 0, normalizedColumnName.length() - 2) + "_" + (_getCountSameValue(normalizedColumnName, mappingColumnName, 1));
193            }
194            
195            mappingColumnName.put(columnName, normalizedColumnName);
196            
197            return normalizedColumnName;
198        }
199    }
200    
201    private int _getCountSameValue(String value, Map<String, String> mappingTable, int count)
202    {
203        String valueAux = value;
204        int countAux = count;
205        for (String val : mappingTable.values())
206        {
207            if (val.equals(valueAux))
208            {
209                countAux++;
210                valueAux = StringUtils.substring(valueAux, 0, valueAux.length() - 2) + "_" + countAux;
211                return _getCountSameValue(valueAux, mappingTable, countAux);
212            }
213        }
214        
215        return countAux;
216    }
217    
218    /**
219     * Get the normalized comment
220     * @param initialComment the initial comment
221     * @param maxLength The maximun number of authorized characters
222     * @param connection the connection
223     * @return normalized comment
224     */
225    protected String normalizedComment (String initialComment, int maxLength, Connection connection)
226    {
227        String normalizedComment = escapeValue(initialComment, connection);
228        
229        if (maxLength != 0 && normalizedComment.length() > maxLength)
230        {
231            normalizedComment = normalizedComment.substring(0, maxLength - 3) + "...";
232        }
233        
234        return normalizedComment;
235    }
236
237    /**
238     * Escape column value or comment
239     * @param value The value or SQL comment
240     * @param connection the connection
241     * @return the escaped value
242     */
243    protected String escapeValue (String value, Connection connection)
244    {
245        String newValue = value;
246        String datatype = ConnectionHelper.getDatabaseType(connection);
247        if (datatype.equals(ConnectionHelper.DATABASE_MYSQL))
248        {
249            newValue = value.replace("\'", "\\'");
250        }
251        else if (datatype.equals(ConnectionHelper.DATABASE_ORACLE))
252        {
253            newValue = value.replace("'", "''");
254        }
255        
256        return newValue;
257    }
258    
259    /**
260     * Get the table name from the cache
261     * @return the mapping for table name
262     */
263    @SuppressWarnings("unchecked")
264    protected Map<String, String> getMappingTableNameFromCache()
265    {
266        Request request = ContextHelper.getRequest(_context);
267        
268        Map<String, String> mappingTableName = new HashMap<>();
269        if (request.getAttribute("mappingTableName") != null)
270        {
271            mappingTableName = (Map<String, String>) request.getAttribute("mappingTableName");
272        }
273        else
274        {
275            request.setAttribute("mappingTableName", mappingTableName);
276        }
277        
278        return mappingTableName;
279    }
280    
281    /**
282     * Get the column name from the cache
283     * @return the mapping for column name
284     */
285    @SuppressWarnings("unchecked")
286    protected Map<String, HashMap<String, String>> getMappingTableColumnNameFromCache()
287    {
288        Request request = ContextHelper.getRequest(_context);
289        
290        Map<String, HashMap<String, String>> mappingTableColumnName = new HashMap<>();
291        if (request.getAttribute("mappingTableColumnName") != null)
292        {
293            mappingTableColumnName = (Map<String, HashMap<String, String>>) request.getAttribute("mappingTableColumnName");
294        }
295        else
296        {
297            request.setAttribute("mappingTableColumnName", mappingTableColumnName);
298        }
299        
300        return mappingTableColumnName;
301    }
302    
303    /**
304     * Normalize the initialName into a name depends on the mapping Policy
305     * @param mappingPolicy (FULL, CAMELCASE, FIRSTCHAR)
306     * @param initialName the initial table name
307     * @return the name changed depends on the mapping Policy
308     */
309    protected String normalizeNameFromPolicy(String mappingPolicy, String initialName)
310    {
311        String modifiedName = "";
312        if (mappingPolicy.equals("FULL"))
313        {
314            modifiedName = initialName;
315        }
316        else if (mappingPolicy.equals("CAMELCASE"))
317        {
318            modifiedName = initialName.replaceAll("(\\p{Ll})(\\p{Lu})", "$1 $2");
319            modifiedName = WordUtils.initials(modifiedName);
320        }
321        else if (mappingPolicy.equals("FIRSTCHAR"))
322        {
323            modifiedName = initialName.replaceAll("(\\p{Ll})(\\p{Lu})", "$1 $2");
324            String[] words = modifiedName.split(" ");
325            
326            StringBuilder finalName = new StringBuilder();
327            for (String word : words)
328            {
329                if (word.length() > 3)
330                {
331                    for (int i = 3; i < word.length(); i++)
332                    {
333                        char charWord = word.charAt(i);
334                        if (Character.isUpperCase(charWord)
335                            || charWord == 'a' || charWord == 'e' || charWord == 'i' 
336                            || charWord == 'o' || charWord == 'u' || charWord == 'y')
337                        {
338                            finalName.append(word.substring(0, i));
339                            break;
340                        }
341                        else if (i == word.length() - 1)
342                        {
343                            finalName.append(word);
344                        }
345                    }
346                }
347                else
348                {
349                    finalName.append(word);
350                }
351            }
352            modifiedName = finalName.toString();
353        }
354        
355        return modifiedName;
356    }
357}