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.util.ArrayList;
023import java.util.Collection;
024import java.util.Date;
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            query.append(" AND ").append(_contextColumn).append(" = ?");
199            
200            stmt = connection.prepareStatement(query.toString());
201            stmt.setString(1, user.getLogin());
202            stmt.setString(2, user.getPopulationId());
203            stmt.setString(3, storageContext);
204            
205            stmt.executeUpdate();
206        }
207        finally
208        {
209            ConnectionHelper.cleanup(stmt);
210        }
211        
212    }
213
214    /**
215     * Remove content from all user preferences
216     * @param contentId the content id to remove
217     * @throws UserPreferencesException if failed to remove user preferences
218     */
219    public void removeContentFromUserPreferences(String contentId) throws UserPreferencesException
220    {
221        Connection connection = null;
222        PreparedStatement stmt = null;
223        
224        try
225        {
226            connection = ConnectionHelper.getConnection(_dataSourceId);
227            
228            StringBuilder query = new StringBuilder();
229            query.append("DELETE FROM ").append(_prefIdToTable.get(ODFCartManager.CART_USER_PREF_CONTENT_IDS)).append(" WHERE ").append(_contentIdColumn).append(" like ?");
230            
231            stmt = connection.prepareStatement(query.toString());
232            
233            stmt.setString(1, contentId + "%");
234            stmt.executeUpdate();
235            
236            ConnectionHelper.cleanup(stmt);
237            
238            query = new StringBuilder();
239            query.append("DELETE FROM ").append(_prefIdToTable.get(ODFCartManager.SUBSCRIPTION_USER_PREF_CONTENT_IDS)).append(" WHERE ").append(_contentIdColumn).append(" like ?");
240            
241            stmt = connection.prepareStatement(query.toString());
242            
243            stmt.setString(1, contentId + "%");
244            stmt.executeUpdate();
245            
246        }
247        catch (SQLException e)
248        {
249            String message = "Database error trying to remove ODF content of id '" + contentId + "' from all user's preferences";
250            getLogger().error(message, e);
251            throw new UserPreferencesException(message, e);
252        }
253        finally
254        {
255            ConnectionHelper.cleanup(stmt);
256            ConnectionHelper.cleanup(connection);
257        }
258    }
259
260    @Override
261    public void setUserPreferences(UserIdentity user, String storageContext, Map<String, String> contextVars, Map<String, String> preferences) throws UserPreferencesException
262    {
263        removeUserPreferences(preferences.keySet(), user, storageContext, contextVars);
264        
265        Connection connection = null;
266        try
267        {
268            connection = ConnectionHelper.getConnection(_dataSourceId);
269            _insertPreferences(connection, preferences, user, storageContext);
270        }
271        catch (SQLException e)
272        {
273            String message = "Database error trying to set ODF cart preferences of user '" + user + "' in context '" + storageContext + "'.";
274            getLogger().error(message, e);
275            throw new UserPreferencesException(message, e);
276        }
277        finally
278        {
279            ConnectionHelper.cleanup(connection);
280        }
281    }
282    
283    private void _insertPreferences(Connection connection, Map<String, String> preferences, UserIdentity user, String storageContext) throws SQLException
284    {
285        for (String id : preferences.keySet())
286        {
287            String table = _prefIdToTable.get(id);
288            
289            String[] contentIdsTab = StringUtils.split(preferences.get(id), ",");
290            
291            if (contentIdsTab.length > 0)
292            {
293                PreparedStatement stmt = null;
294                try
295                {
296                    StringBuilder query = new StringBuilder();
297                    query.append("INSERT INTO ").append(table).append("(").append(_loginColumn).append(", ").append(_populationColumn);
298                    query.append(", ").append(_contextColumn);
299                    query.append(", ").append(_contentIdColumn);
300    
301                    StringBuilder values = new StringBuilder();
302                    for (int i = 0; i < contentIdsTab.length; i++)
303                    {
304                        if (i != 0)
305                        {
306                            values.append(",");
307                        }
308                        values.append("(?, ?, ?, ?)");
309                    }
310                    
311                    query.append(") VALUES ").append(values);
312                    
313                    int i = 1;
314                    stmt = connection.prepareStatement(query.toString());
315                    
316                    for (String idContent : contentIdsTab)
317                    {
318                        stmt.setString(i++, user.getLogin());
319                        stmt.setString(i++, user.getPopulationId());
320                        stmt.setString(i++, storageContext);
321                        stmt.setString(i++, idContent);
322                    }
323                    
324                    stmt.executeUpdate();
325                }
326                finally
327                {
328                    ConnectionHelper.cleanup(stmt);
329                }
330            }
331        }
332        
333    }
334
335    @Override
336    public String getUserPreferenceAsString(UserIdentity user, String storageContext, Map<String, String> contextVars, String id) throws UserPreferencesException
337    {
338        Connection connection = null;
339        PreparedStatement statement = null;
340        ResultSet rs = null;
341        String value = null;
342        String table = _prefIdToTable.get(id);
343        
344        try
345        {
346            StringBuilder query = new StringBuilder();
347            query.append("SELECT ").append(_contentIdColumn).append(" FROM ").append(table).append(" WHERE ").append(_loginColumn).append(" = ? AND ").append(_populationColumn).append(" = ?");
348            query.append(" AND ").append(_contextColumn).append(" = ?");
349            
350            connection = ConnectionHelper.getConnection(_dataSourceId);
351            
352            statement = connection.prepareStatement(query.toString());
353            statement.setString(1, user.getLogin());
354            statement.setString(2, user.getPopulationId());
355            statement.setString(3, storageContext);
356            
357            rs = statement.executeQuery();
358            
359            List<String> results = new ArrayList<>();
360            while (rs.next())
361            {
362                results.add(rs.getString(1));
363            }
364            
365            value = StringUtils.join(results, ",");
366        }
367        catch (SQLException e)
368        {
369            String message = "Database error trying to get ODF cart preferences of user '" + user + "' in context '" + storageContext + "'.";
370            getLogger().error(message, e);
371            throw new UserPreferencesException(message, e);
372        }
373        finally
374        {
375            ConnectionHelper.cleanup(rs);
376            ConnectionHelper.cleanup(statement);
377            ConnectionHelper.cleanup(connection);
378        }
379        
380        return value;
381    }
382    
383    @Override
384    public Long getUserPreferenceAsLong(UserIdentity user, String storageContext, Map<String, String> contextVars, String id) throws UserPreferencesException
385    {
386        return null;
387    }
388
389    @Override
390    public Date getUserPreferenceAsDate(UserIdentity user, String storageContext, Map<String, String> contextVars, String id) throws UserPreferencesException
391    {
392        return null;
393    }
394
395    @Override
396    public Boolean getUserPreferenceAsBoolean(UserIdentity user, String storageContext, Map<String, String> contextVars, String id) throws UserPreferencesException
397    {
398        return null;
399    }
400
401    @Override
402    public Double getUserPreferenceAsDouble(UserIdentity user, String storageContext, Map<String, String> contextVars, String id) throws UserPreferencesException
403    {
404        return null;
405    }
406
407}