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