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 public Map<UserIdentity, Map<String, String>> getAllUnTypedUserPrefs(String storageContext, Map<String, String> contextVars) throws UserPreferencesException 123 { 124 Map<UserIdentity, Map<String, String>> allUserPrefs = new HashMap<>(); 125 126 for (String id : _prefIdToTable.keySet()) 127 { 128 Map<UserIdentity, List<String>> preferences = _getUserPreferences(null, storageContext, id); 129 for (UserIdentity user : preferences.keySet()) 130 { 131 Map<String, String> userPrefs = allUserPrefs.computeIfAbsent(user, __ -> new HashMap<>()); 132 userPrefs.put(id, StringUtils.join(preferences.get(user), ",")); 133 } 134 } 135 136 return allUserPrefs; 137 } 138 139 private Map<UserIdentity, List<String>> _getUserPreferences(UserIdentity user, String storageContext, String id) throws UserPreferencesException 140 { 141 Map<UserIdentity, List<String>> userResults = new HashMap<>(); 142 143 Connection connection = null; 144 PreparedStatement statement = null; 145 ResultSet rs = null; 146 String table = _prefIdToTable.get(id); 147 148 try 149 { 150 StringBuilder query = new StringBuilder(); 151 query.append("SELECT ").append(" * FROM ").append(table).append(" WHERE ").append(_contextColumn).append(" = ?"); 152 153 if (user != null) 154 { 155 query.append(" AND ").append(_loginColumn).append(" = ? AND ").append(_populationColumn).append(" = ?"); 156 } 157 158 connection = ConnectionHelper.getConnection(_dataSourceId); 159 160 statement = connection.prepareStatement(query.toString()); 161 162 int index = 1; 163 statement.setString(index, storageContext); 164 index++; 165 166 if (user != null) 167 { 168 statement.setString(index, user.getLogin()); 169 index++; 170 statement.setString(index, user.getPopulationId()); 171 } 172 173 rs = statement.executeQuery(); 174 175 while (rs.next()) 176 { 177 UserIdentity u = _getUserIdentity(rs); 178 List<String> results = userResults.computeIfAbsent(u, __ -> new ArrayList<>()); 179 results.add(rs.getString(_contentIdColumn)); 180 } 181 } 182 catch (SQLException e) 183 { 184 String message = user == null 185 ? "Database error trying to get ODF cart all user' preferences in context '" + storageContext + "'." 186 : "Database error trying to get ODF cart preferences of user '" + user + "' in context '" + storageContext + "'."; 187 getLogger().error(message, e); 188 throw new UserPreferencesException(message, e); 189 } 190 finally 191 { 192 ConnectionHelper.cleanup(rs); 193 ConnectionHelper.cleanup(statement); 194 ConnectionHelper.cleanup(connection); 195 } 196 197 return userResults; 198 } 199 200 private UserIdentity _getUserIdentity(ResultSet rs) throws SQLException 201 { 202 String login = rs.getString(_loginColumn); 203 String population = rs.getString(_populationColumn); 204 return new UserIdentity(login, population); 205 } 206 207 @Override 208 public Map<String, String> getUnTypedUserPrefs(UserIdentity user, String storageContext, Map<String, String> contextVars) throws UserPreferencesException 209 { 210 Map<String, String> prefs = new HashMap<>(); 211 212 for (String id : _prefIdToTable.keySet()) 213 { 214 prefs.put(id, getUserPreferenceAsString(user, storageContext, contextVars, id)); 215 } 216 217 return prefs; 218 } 219 220 /** 221 * Remove the stored user preferences for a login in a given context. 222 * @param prefIds the id of user preferences to remove 223 * @param user the user. 224 * @param storageContext the preferences storage context. 225 * @param contextVars the context variables. 226 * @throws UserPreferencesException if an error occurred 227 */ 228 public void removeUserPreferences(Collection<String> prefIds, UserIdentity user, String storageContext, Map<String, String> contextVars) throws UserPreferencesException 229 { 230 Connection connection = null; 231 try 232 { 233 connection = ConnectionHelper.getConnection(_dataSourceId); 234 for (String id : prefIds) 235 { 236 _removeUserPreferencesFromTable(connection, user, storageContext, _prefIdToTable.get(id)); 237 } 238 } 239 catch (SQLException e) 240 { 241 String message = "Database error trying to remove ODF cart preferences for user '" + user + "' in context '" + storageContext + "'."; 242 getLogger().error(message, e); 243 throw new UserPreferencesException(message, e); 244 } 245 finally 246 { 247 ConnectionHelper.cleanup(connection); 248 } 249 } 250 251 @Override 252 public void removeUserPreferences(UserIdentity user, String storageContext, Map<String, String> contextVars) throws UserPreferencesException 253 { 254 Connection connection = null; 255 try 256 { 257 connection = ConnectionHelper.getConnection(_dataSourceId); 258 for (String id : _prefIdToTable.keySet()) 259 { 260 _removeUserPreferencesFromTable(connection, user, storageContext, _prefIdToTable.get(id)); 261 } 262 } 263 catch (SQLException e) 264 { 265 String message = "Database error trying to remove ODF cart preferences for user '" + user + "' in context '" + storageContext + "'."; 266 getLogger().error(message, e); 267 throw new UserPreferencesException(message, e); 268 } 269 finally 270 { 271 ConnectionHelper.cleanup(connection); 272 } 273 } 274 275 private void _removeUserPreferencesFromTable(Connection connection, UserIdentity user, String storageContext, String table) throws SQLException 276 { 277 PreparedStatement stmt = null; 278 279 try 280 { 281 StringBuilder query = new StringBuilder(); 282 query.append("DELETE FROM ").append(table).append(" WHERE ").append(_loginColumn).append(" = ? AND ").append(_populationColumn).append(" = ?"); 283 if (storageContext != null) 284 { 285 query.append(" AND ").append(_contextColumn).append(" = ?"); 286 } 287 288 stmt = connection.prepareStatement(query.toString()); 289 stmt.setString(1, user.getLogin()); 290 stmt.setString(2, user.getPopulationId()); 291 if (storageContext != null) 292 { 293 stmt.setString(3, storageContext); 294 } 295 296 stmt.executeUpdate(); 297 } 298 finally 299 { 300 ConnectionHelper.cleanup(stmt); 301 } 302 303 } 304 305 /** 306 * Remove content from all user preferences 307 * @param contentId the content id to remove 308 * @throws UserPreferencesException if failed to remove user preferences 309 */ 310 public void removeContentFromUserPreferences(String contentId) throws UserPreferencesException 311 { 312 Connection connection = null; 313 PreparedStatement stmt = null; 314 315 try 316 { 317 connection = ConnectionHelper.getConnection(_dataSourceId); 318 319 StringBuilder query = new StringBuilder(); 320 query.append("DELETE FROM ").append(_prefIdToTable.get(ODFCartManager.CART_USER_PREF_CONTENT_IDS)).append(" WHERE ").append(_contentIdColumn).append(" like ?"); 321 322 stmt = connection.prepareStatement(query.toString()); 323 324 stmt.setString(1, contentId + "%"); 325 stmt.executeUpdate(); 326 327 ConnectionHelper.cleanup(stmt); 328 329 query = new StringBuilder(); 330 query.append("DELETE FROM ").append(_prefIdToTable.get(ODFCartManager.SUBSCRIPTION_USER_PREF_CONTENT_IDS)).append(" WHERE ").append(_contentIdColumn).append(" like ?"); 331 332 stmt = connection.prepareStatement(query.toString()); 333 334 stmt.setString(1, contentId + "%"); 335 stmt.executeUpdate(); 336 337 } 338 catch (SQLException e) 339 { 340 String message = "Database error trying to remove ODF content of id '" + contentId + "' from all user's preferences"; 341 getLogger().error(message, e); 342 throw new UserPreferencesException(message, e); 343 } 344 finally 345 { 346 ConnectionHelper.cleanup(stmt); 347 ConnectionHelper.cleanup(connection); 348 } 349 } 350 351 @Override 352 public void setUserPreferences(UserIdentity user, String storageContext, Map<String, String> contextVars, Map<String, String> preferences) throws UserPreferencesException 353 { 354 removeUserPreferences(preferences.keySet(), user, storageContext, contextVars); 355 356 Connection connection = null; 357 try 358 { 359 connection = ConnectionHelper.getConnection(_dataSourceId); 360 _insertPreferences(connection, preferences, user, storageContext); 361 } 362 catch (SQLException e) 363 { 364 String message = "Database error trying to set ODF cart preferences of user '" + user + "' in context '" + storageContext + "'."; 365 getLogger().error(message, e); 366 throw new UserPreferencesException(message, e); 367 } 368 finally 369 { 370 ConnectionHelper.cleanup(connection); 371 } 372 } 373 374 private void _insertPreferences(Connection connection, Map<String, String> preferences, UserIdentity user, String storageContext) throws SQLException 375 { 376 for (String id : preferences.keySet()) 377 { 378 String table = _prefIdToTable.get(id); 379 380 String[] contentIdsTab = StringUtils.split(preferences.get(id), ","); 381 382 if (contentIdsTab.length > 0) 383 { 384 PreparedStatement stmt = null; 385 try 386 { 387 StringBuilder query = new StringBuilder(); 388 query.append("INSERT INTO ").append(table).append("(").append(_loginColumn).append(", ").append(_populationColumn); 389 query.append(", ").append(_contextColumn); 390 query.append(", ").append(_contentIdColumn); 391 392 StringBuilder values = new StringBuilder(); 393 for (int i = 0; i < contentIdsTab.length; i++) 394 { 395 if (i != 0) 396 { 397 values.append(","); 398 } 399 values.append("(?, ?, ?, ?)"); 400 } 401 402 query.append(") VALUES ").append(values); 403 404 int i = 1; 405 stmt = connection.prepareStatement(query.toString()); 406 407 for (String idContent : contentIdsTab) 408 { 409 stmt.setString(i++, user.getLogin()); 410 stmt.setString(i++, user.getPopulationId()); 411 stmt.setString(i++, storageContext); 412 stmt.setString(i++, idContent); 413 } 414 415 stmt.executeUpdate(); 416 } 417 finally 418 { 419 ConnectionHelper.cleanup(stmt); 420 } 421 } 422 } 423 } 424 425 @Override 426 public String getUserPreferenceAsString(UserIdentity user, String storageContext, Map<String, String> contextVars, String id) throws UserPreferencesException 427 { 428 if (user == null) 429 { 430 return null; 431 } 432 433 Map<UserIdentity, List<String>> userPreferences = _getUserPreferences(user, storageContext, id); 434 List<String> values = userPreferences.getOrDefault(user, null); 435 return values != null ? StringUtils.join(values, ",") : null; 436 } 437 438 @Override 439 public Long getUserPreferenceAsLong(UserIdentity user, String storageContext, Map<String, String> contextVars, String id) throws UserPreferencesException 440 { 441 return null; 442 } 443 444 @Override 445 public ZonedDateTime getUserPreferenceAsDate(UserIdentity user, String storageContext, Map<String, String> contextVars, String id) throws UserPreferencesException 446 { 447 return null; 448 } 449 450 @Override 451 public Boolean getUserPreferenceAsBoolean(UserIdentity user, String storageContext, Map<String, String> contextVars, String id) throws UserPreferencesException 452 { 453 return null; 454 } 455 456 @Override 457 public Double getUserPreferenceAsDouble(UserIdentity user, String storageContext, Map<String, String> contextVars, String id) throws UserPreferencesException 458 { 459 return null; 460 } 461 462}