001/*
002 *  Copyright 2016 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.odfweb.userpref;
017
018import java.sql.Connection;
019import java.sql.PreparedStatement;
020import java.sql.ResultSet;
021import java.sql.SQLException;
022import java.util.ArrayList;
023import java.util.Date;
024import java.util.HashMap;
025import java.util.List;
026import java.util.Map;
027
028import org.apache.avalon.framework.component.Component;
029import org.apache.avalon.framework.configuration.Configurable;
030import org.apache.avalon.framework.configuration.Configuration;
031import org.apache.avalon.framework.configuration.ConfigurationException;
032import org.apache.avalon.framework.logger.AbstractLogEnabled;
033import org.apache.commons.lang.StringUtils;
034
035import org.ametys.core.datasource.ConnectionHelper;
036import org.ametys.core.user.UserIdentity;
037import org.ametys.core.userpref.UserPreferencesException;
038import org.ametys.core.userpref.UserPreferencesStorage;
039import org.ametys.odf.course.Course;
040import org.ametys.odf.program.Program;
041import org.ametys.runtime.config.Config;
042
043
044/**
045 * Specific storage for odf cart user preferences
046 */
047public class ODFCartUserPreferencesStorage extends AbstractLogEnabled implements UserPreferencesStorage, Configurable, Component
048{
049    /** The Avalon Role */
050    public static final String ROLE = ODFCartUserPreferencesStorage.class.getName();
051    
052    /** The id of user preference for ids for programs */
053    public static final String USER_PREF_PROGRAM_IDS = "cartProgramIds";
054    /** The id of user preference for ids for courses */
055    public static final String USER_PREF_COURSE_IDS = "cartCourseIds";
056    
057    /** The id of the data source used. */
058    protected String _dataSourceId;
059    
060    /** The login column, cannot be null. */
061    protected String _loginColumn;
062    
063    /** The population id column, cannot be null. */
064    protected String _populationColumn;
065    
066    /** The context column, can be null if the database is not context-dependent. */
067    protected String _contextColumn;
068    
069    /** The content id column */
070    protected String _contentIdColumn;
071    
072    /** Mapping from preference id to table name. */
073    protected Map<String, String> _prefIdToTable;
074
075    @Override
076    public void configure(Configuration configuration) throws ConfigurationException
077    {
078        // Data source id
079        Configuration dataSourceConf = configuration.getChild("datasource", false);
080        if (dataSourceConf == null)
081        {
082            throw new ConfigurationException("The 'datasource' configuration node must be defined.", dataSourceConf);
083        }
084        
085        String dataSourceConfParam = dataSourceConf.getValue();
086        String dataSourceConfType = dataSourceConf.getAttribute("type", "config");
087        
088        if (StringUtils.equals(dataSourceConfType, "config"))
089        {
090            _dataSourceId = Config.getInstance().getValueAsString(dataSourceConfParam);
091        }
092        else // expecting type="id"
093        {
094            _dataSourceId = dataSourceConfParam;
095        }
096        
097        // Default to "contentId".
098        _contentIdColumn = configuration.getChild("content-id").getValue("contentId");
099        // Default to "login".
100        _loginColumn = configuration.getChild("loginColumn").getValue("login").toLowerCase();
101        // Default to "population"
102        _populationColumn = configuration.getChild("populationColumn").getValue("population").toLowerCase();
103        // Default to 'context' (no context column).
104        _contextColumn = configuration.getChild("contextColumn").getValue("context");
105        
106        // Configure the preference-table mappings.
107        configureMappings(configuration.getChild("mappings"));
108    }
109    
110    /**
111     * Configure the mappings from preference ID to table name.
112     * @param configuration the mapping configuration root.
113     * @throws ConfigurationException if an error occurs.
114     */
115    public void configureMappings(Configuration configuration) throws ConfigurationException
116    {
117        _prefIdToTable = new HashMap<>();
118        
119        for (Configuration mappingConf : configuration.getChildren("mapping"))
120        {
121            String prefId = mappingConf.getAttribute("prefId");
122            String table = mappingConf.getAttribute("table");
123            
124            _prefIdToTable.put(prefId, table);
125        }
126    }
127    
128    @Override
129    public Map<String, String> getUnTypedUserPrefs(UserIdentity user, String storageContext, Map<String, String> contextVars) throws UserPreferencesException
130    {
131        Map<String, String> prefs = new HashMap<>();
132        
133        for (String id : _prefIdToTable.keySet())
134        {
135            prefs.put(id, getUserPreferenceAsString(user, storageContext, contextVars, id));
136        }
137        
138        return prefs;
139    }
140    
141    @Override
142    public void removeUserPreferences(UserIdentity user, String storageContext, Map<String, String> contextVars) throws UserPreferencesException
143    {
144        Connection connection = null;
145        try
146        {
147            connection = ConnectionHelper.getConnection(_dataSourceId);
148            for (String id : _prefIdToTable.keySet())
149            {
150                _removeUserPreferencesFromTable(connection, user, storageContext, _prefIdToTable.get(id));
151            }
152        }
153        catch (SQLException e)
154        {
155            String message = "Database error trying to remove ODF cart preferences for user '" + user + "' in context '" + storageContext + "'.";
156            getLogger().error(message, e);
157            throw new UserPreferencesException(message, e);
158        }
159        finally
160        {
161            ConnectionHelper.cleanup(connection);
162        }
163    }
164
165    private void _removeUserPreferencesFromTable(Connection connection, UserIdentity user, String storageContext, String table) throws SQLException
166    {
167        PreparedStatement stmt = null;
168        
169        try
170        {
171            StringBuilder query = new StringBuilder();
172            query.append("DELETE FROM ").append(table).append(" WHERE ").append(_loginColumn).append(" = ? AND ").append(_populationColumn).append(" = ?");
173            query.append(" AND ").append(_contextColumn).append(" = ?");
174            
175            stmt = connection.prepareStatement(query.toString());
176            stmt.setString(1, user.getLogin());
177            stmt.setString(2, user.getPopulationId());
178            stmt.setString(3, storageContext);
179            
180            stmt.executeUpdate();
181        }
182        finally
183        {
184            ConnectionHelper.cleanup(stmt);
185        }
186        
187    }
188    
189    /**
190     * Remove all user preferences referencing a {@link Program}
191     * @param programId The id of program to remove from user preferences
192     * @throws UserPreferencesException if failed to delete users preferences
193     */
194    public void removeProgramFromUserPreferences(String programId) throws UserPreferencesException
195    {
196        _removeContentFromUserPreferences(programId, _prefIdToTable.get(USER_PREF_PROGRAM_IDS));
197    }
198    
199    /**
200     * Remove all user preferences referencing a {@link Course}
201     * @param courseId The id of course to remove from user preferences
202     * @throws UserPreferencesException if failed to delete users preferences
203     */
204    public void removeCourseFromUserPreferences(String courseId) throws UserPreferencesException
205    {
206        _removeContentFromUserPreferences(courseId, _prefIdToTable.get(USER_PREF_COURSE_IDS));
207    }
208    
209    private void _removeContentFromUserPreferences(String contentId, String tableName) throws UserPreferencesException
210    {
211        Connection connection = null;
212        PreparedStatement stmt = null;
213        
214        try
215        {
216            connection = ConnectionHelper.getConnection(_dataSourceId);
217            
218            StringBuilder query = new StringBuilder();
219            query.append("DELETE FROM ").append(tableName).append(" WHERE ").append(_contentIdColumn).append(" = ?");
220            
221            stmt = connection.prepareStatement(query.toString());
222            
223            stmt.setString(1, contentId);
224            stmt.executeUpdate();
225        }
226        catch (SQLException e)
227        {
228            String message = "Database error trying to remove ODF content of id '" + contentId + "' from all user's preferences";
229            getLogger().error(message, e);
230            throw new UserPreferencesException(message, e);
231        }
232        finally
233        {
234            ConnectionHelper.cleanup(stmt);
235            ConnectionHelper.cleanup(connection);
236        }
237    }
238
239    @Override
240    public void setUserPreferences(UserIdentity user, String storageContext, Map<String, String> contextVars, Map<String, String> preferences) throws UserPreferencesException
241    {
242        removeUserPreferences(user, storageContext, contextVars);
243        
244        Connection connection = null;
245        try
246        {
247            connection = ConnectionHelper.getConnection(_dataSourceId);
248            _insertPreferences(connection, preferences, user, storageContext);
249        }
250        catch (SQLException e)
251        {
252            String message = "Database error trying to set ODF cart preferences of user '" + user + "' in context '" + storageContext + "'.";
253            getLogger().error(message, e);
254            throw new UserPreferencesException(message, e);
255        }
256        finally
257        {
258            ConnectionHelper.cleanup(connection);
259        }
260    }
261    
262    private void _insertPreferences(Connection connection, Map<String, String> preferences, UserIdentity user, String storageContext) throws SQLException
263    {
264        for (String id : preferences.keySet())
265        {
266            String table = _prefIdToTable.get(id);
267            
268            String[] contentIdsTab = StringUtils.split(preferences.get(id), ",");
269            
270            if (contentIdsTab.length > 0)
271            {
272                PreparedStatement stmt = null;
273                try
274                {
275                    StringBuilder query = new StringBuilder();
276                    query.append("INSERT INTO ").append(table).append("(").append(_loginColumn).append(", ").append(_populationColumn);
277                    query.append(", ").append(_contextColumn);
278                    query.append(", ").append(_contentIdColumn);
279    
280                    StringBuilder values = new StringBuilder();
281                    for (int i = 0; i < contentIdsTab.length; i++)
282                    {
283                        if (i != 0)
284                        {
285                            values.append(",");
286                        }
287                        values.append("(?, ?, ?, ?)");
288                    }
289                    
290                    query.append(") VALUES ").append(values);
291                    
292                    int i = 1;
293                    stmt = connection.prepareStatement(query.toString());
294                    
295                    for (String idContent : contentIdsTab)
296                    {
297                        stmt.setString(i++, user.getLogin());
298                        stmt.setString(i++, user.getPopulationId());
299                        stmt.setString(i++, storageContext);
300                        stmt.setString(i++, idContent);
301                    }
302                    
303                    stmt.executeUpdate();
304                }
305                finally
306                {
307                    ConnectionHelper.cleanup(stmt);
308                }
309            }
310        }
311        
312    }
313
314    @Override
315    public String getUserPreferenceAsString(UserIdentity user, String storageContext, Map<String, String> contextVars, String id) throws UserPreferencesException
316    {
317        Connection connection = null;
318        PreparedStatement statement = null;
319        ResultSet rs = null;
320        String value = null;
321        String table = _prefIdToTable.get(id);
322        
323        try
324        {
325            StringBuilder query = new StringBuilder();
326            query.append("SELECT ").append(_contentIdColumn).append(" FROM ").append(table).append(" WHERE ").append(_loginColumn).append(" = ? AND ").append(_populationColumn).append(" = ?");
327            query.append(" AND ").append(_contextColumn).append(" = ?");
328            
329            connection = ConnectionHelper.getConnection(_dataSourceId);
330            
331            statement = connection.prepareStatement(query.toString());
332            statement.setString(1, user.getLogin());
333            statement.setString(2, user.getPopulationId());
334            statement.setString(3, storageContext);
335            
336            rs = statement.executeQuery();
337            
338            List<String> results = new ArrayList<>();
339            while (rs.next())
340            {
341                results.add(rs.getString(1));
342            }
343            
344            value = StringUtils.join(results, ",");
345        }
346        catch (SQLException e)
347        {
348            String message = "Database error trying to get ODF cart preferences of user '" + user + "' in context '" + storageContext + "'.";
349            getLogger().error(message, e);
350            throw new UserPreferencesException(message, e);
351        }
352        finally
353        {
354            ConnectionHelper.cleanup(rs);
355            ConnectionHelper.cleanup(statement);
356            ConnectionHelper.cleanup(connection);
357        }
358        
359        return value;
360    }
361    
362    @Override
363    public Long getUserPreferenceAsLong(UserIdentity user, String storageContext, Map<String, String> contextVars, String id) throws UserPreferencesException
364    {
365        return null;
366    }
367
368    @Override
369    public Date getUserPreferenceAsDate(UserIdentity user, String storageContext, Map<String, String> contextVars, String id) throws UserPreferencesException
370    {
371        return null;
372    }
373
374    @Override
375    public Boolean getUserPreferenceAsBoolean(UserIdentity user, String storageContext, Map<String, String> contextVars, String id) throws UserPreferencesException
376    {
377        return null;
378    }
379
380    @Override
381    public Double getUserPreferenceAsDouble(UserIdentity user, String storageContext, Map<String, String> contextVars, String id) throws UserPreferencesException
382    {
383        return null;
384    }
385
386}