001/* 002 * Copyright 2012 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.newsletter.userpref; 017 018import java.time.ZonedDateTime; 019import java.util.ArrayList; 020import java.util.Arrays; 021import java.util.Date; 022import java.util.HashMap; 023import java.util.HashSet; 024import java.util.List; 025import java.util.Map; 026import java.util.Objects; 027import java.util.Set; 028import java.util.UUID; 029 030import org.apache.avalon.framework.component.Component; 031import org.apache.avalon.framework.logger.AbstractLogEnabled; 032import org.apache.avalon.framework.service.ServiceException; 033import org.apache.avalon.framework.service.ServiceManager; 034import org.apache.avalon.framework.service.Serviceable; 035 036import org.ametys.core.user.User; 037import org.ametys.core.user.UserIdentity; 038import org.ametys.core.user.UserManager; 039import org.ametys.core.user.directory.NotUniqueUserException; 040import org.ametys.core.user.population.PopulationContextHelper; 041import org.ametys.core.userpref.UserPreferencesException; 042import org.ametys.core.userpref.UserPreferencesStorage; 043import org.ametys.plugins.newsletter.category.CategoryProviderExtensionPoint; 044import org.ametys.plugins.newsletter.daos.Subscriber; 045import org.ametys.plugins.newsletter.daos.SubscribersDAO; 046import org.ametys.plugins.newsletter.daos.SubscribersDAO.UnsubscribeOrigin; 047import org.ametys.plugins.repository.AmetysObjectIterable; 048import org.ametys.web.repository.site.Site; 049import org.ametys.web.repository.site.SiteManager; 050import org.ametys.web.userpref.FOUserPreferencesConstants; 051 052/** 053 * Retrieves and stores newsletter user preferences values as subscriptions. 054 */ 055public class NewsletterUserPreferencesStorage extends AbstractLogEnabled implements UserPreferencesStorage, Component, Serviceable 056{ 057 /** The front-office users manager. */ 058 protected UserManager _foUserManager; 059 060 /** The category provider extension point. */ 061 protected CategoryProviderExtensionPoint _categoryEP; 062 063 /** The site manager */ 064 protected SiteManager _siteManager; 065 066 /** The subscribers DAO. */ 067 protected SubscribersDAO _subscribersDao; 068 069 /** The population context helper */ 070 protected PopulationContextHelper _populationContextHelper; 071 072 @Override 073 public void service(ServiceManager serviceManager) throws ServiceException 074 { 075 _foUserManager = (UserManager) serviceManager.lookup(UserManager.ROLE); 076 _categoryEP = (CategoryProviderExtensionPoint) serviceManager.lookup(CategoryProviderExtensionPoint.ROLE); 077 _populationContextHelper = (PopulationContextHelper) serviceManager.lookup(PopulationContextHelper.ROLE); 078 _siteManager = (SiteManager) serviceManager.lookup(SiteManager.ROLE); 079 _subscribersDao = (SubscribersDAO) serviceManager.lookup(SubscribersDAO.ROLE); 080 } 081 082 public Map<UserIdentity, Map<String, String>> getAllUnTypedUserPrefs(String storageContext, Map<String, String> contextVars) throws UserPreferencesException 083 { 084 return _getUnTypedUserPrefs(null, storageContext, contextVars); 085 } 086 087 @Override 088 public Map<String, String> getUnTypedUserPrefs(UserIdentity userIdentity, String storageContext, Map<String, String> contextVars) throws UserPreferencesException 089 { 090 if (userIdentity == null) 091 { 092 return new HashMap<>(); 093 } 094 095 Map<UserIdentity, Map<String,String>> unTypedUserPrefs = _getUnTypedUserPrefs(userIdentity, storageContext, contextVars); 096 return unTypedUserPrefs.getOrDefault(userIdentity, new HashMap<>()); 097 } 098 099 private Map<UserIdentity, Map<String, String>> _getUnTypedUserPrefs(UserIdentity user, String storageContext, Map<String, String> contextVars) throws UserPreferencesException 100 { 101 Map<UserIdentity, Map<String, String>> userPrefs = new HashMap<>(); 102 103 try 104 { 105 String siteName = contextVars.get(FOUserPreferencesConstants.CONTEXT_VAR_SITENAME); 106 if (siteName != null) 107 { 108 for (User u : _getUsers(siteName, user)) 109 { 110 Map<String, String> preferenceValues = new HashMap<>(); 111 112 List<Subscriber> subscriptions = _subscribersDao.getSubscriptions(u.getEmail(), siteName); 113 for (Subscriber subscription : subscriptions) 114 { 115 preferenceValues.put(subscription.getCategoryId(), "true"); 116 } 117 118 userPrefs.put(u.getIdentity(), preferenceValues); 119 } 120 } 121 return userPrefs; 122 } 123 catch (Exception e) 124 { 125 String message = user == null 126 ? "Error getting newsletter all user's preferences in context " + storageContext 127 : "Error getting newsletter user preferences for login " + user + " and context " + storageContext; 128 getLogger().error(message, e); 129 throw new UserPreferencesException(message, e); 130 } 131 } 132 133 private List<User> _getUsers(String siteName, UserIdentity userIdentity) 134 { 135 if (userIdentity != null) 136 { 137 User user = _foUserManager.getUser(userIdentity.getPopulationId(), userIdentity.getLogin()); 138 return user != null ? List.of(user) : List.of(); 139 } 140 141 Set<String> userPopulationsOnSite = _populationContextHelper.getUserPopulationsOnContexts(Arrays.asList("/sites/" + siteName, "/sites-fo/" + siteName), false, false); 142 return _subscribersDao.getSubscribers() 143 .stream() 144 .map(s -> { 145 try 146 { 147 return _foUserManager.getUserByEmail(userPopulationsOnSite, s.getEmail()); 148 } 149 catch (NotUniqueUserException e) 150 { 151 getLogger().warn("Can get user preference fo user with mail '" + s.getEmail() + "' because several users is found", e); 152 return null; 153 } 154 }) 155 .filter(Objects::nonNull) 156 .toList(); 157 } 158 159 @Override 160 public void setUserPreferences(UserIdentity userIdentity, String storageContext, Map<String, String> contextVars, Map<String, String> preferences) throws UserPreferencesException 161 { 162 try 163 { 164 User user = _foUserManager.getUser(userIdentity.getPopulationId(), userIdentity.getLogin()); 165 String siteName = contextVars.get(FOUserPreferencesConstants.CONTEXT_VAR_SITENAME); 166 167 if (user != null && siteName != null) 168 { 169 List<Subscriber> newSubscribers = new ArrayList<>(); 170 Set<String> removeTokens = new HashSet<>(); 171 172 Map<String, String> existingCategoryIds = getExistingCategoryIds(user.getEmail(), siteName); 173 174 for (String categoryId : preferences.keySet()) 175 { 176 String value = preferences.get(categoryId); 177 178 if (value.equals("true") && _categoryEP.hasCategory(categoryId) && !existingCategoryIds.containsKey(categoryId)) 179 { 180 Subscriber subscriber = getSubscription(siteName, user, categoryId); 181 newSubscribers.add(subscriber); 182 } 183 else if (!value.equals("true") && _categoryEP.hasCategory(categoryId) && existingCategoryIds.containsKey(categoryId)) 184 { 185 String token = existingCategoryIds.get(categoryId); 186 removeTokens.add(token); 187 } 188 } 189 190 // Modify the subscriptions. 191 _subscribersDao.modifySubscriptions(newSubscribers, removeTokens, UnsubscribeOrigin.SUBSCRIBER); 192 } 193 } 194 catch (Exception e) 195 { 196 String message = "Error setting newsletter user preferences for login " + userIdentity + " and context " + storageContext; 197 getLogger().error(message, e); 198 throw new UserPreferencesException(message, e); 199 } 200 } 201 202 @Override 203 public void removeUserPreferences(UserIdentity userIdentity, String storageContext, Map<String, String> contextVars) throws UserPreferencesException 204 { 205 try 206 { 207 User user = _foUserManager.getUser(userIdentity.getPopulationId(), userIdentity.getLogin()); 208 209 if (user != null) 210 { 211 String email = user.getEmail(); 212 if (email != null) 213 { 214 String siteName = contextVars.get(FOUserPreferencesConstants.CONTEXT_VAR_SITENAME); 215 if (siteName != null) 216 { 217 // Remove the subscriptions. 218 _subscribersDao.unsubscribe(email, storageContext, UnsubscribeOrigin.DATAPOLICY); 219 } 220 else 221 { 222 try (AmetysObjectIterable<Site> sites = _siteManager.getSites()) 223 { 224 for (Site site : sites) 225 { 226 // Before unsubscribing, check that no other user use the same email on the site 227 siteName = site.getName(); 228 Set<String> userPopulationsOnSite = _populationContextHelper.getUserPopulationsOnContexts(Arrays.asList("/sites/" + siteName, "/sites-fo/" + siteName), false, false); 229 try 230 { 231 if (_foUserManager.getUserByEmail(userPopulationsOnSite, email) == null) 232 { 233 _subscribersDao.unsubscribe(email, siteName, UnsubscribeOrigin.DATAPOLICY); 234 } 235 } 236 catch (NotUniqueUserException e) 237 { 238 // Do nothing, this email is still matching existing users 239 } 240 } 241 } 242 } 243 } 244 } 245 } 246 catch (Exception e) 247 { 248 String message = "Error removing newsletter subscriptions for login " + userIdentity + " and context " + storageContext; 249 getLogger().error(message, e); 250 throw new UserPreferencesException(message, e); 251 } 252 } 253 254 @Override 255 public String getUserPreferenceAsString(UserIdentity userIdentity, String storageContext, Map<String, String> contextVars, String id) throws UserPreferencesException 256 { 257 String value = null; 258 259 Boolean booleanValue = getUserPreferenceAsBoolean(userIdentity, storageContext, contextVars, id); 260 261 if (booleanValue != null) 262 { 263 value = booleanValue.toString(); 264 } 265 266 return value; 267 } 268 269 @Override 270 public Long getUserPreferenceAsLong(UserIdentity userIdentity, String storageContext, Map<String, String> contextVars, String id) throws UserPreferencesException 271 { 272 return null; 273 } 274 275 @Override 276 public ZonedDateTime getUserPreferenceAsDate(UserIdentity userIdentity, String storageContext, Map<String, String> contextVars, String id) throws UserPreferencesException 277 { 278 return null; 279 } 280 281 @Override 282 public Boolean getUserPreferenceAsBoolean(UserIdentity userIdentity, String storageContext, Map<String, String> contextVars, String id) throws UserPreferencesException 283 { 284 try 285 { 286 Boolean value = null; 287 288 User user = _foUserManager.getUser(userIdentity.getPopulationId(), userIdentity.getLogin()); 289 String siteName = contextVars.get(FOUserPreferencesConstants.CONTEXT_VAR_SITENAME); 290 291 if (user != null && siteName != null) 292 { 293 Subscriber subscriber = _subscribersDao.getSubscriber(user.getEmail(), siteName, id); 294 295 value = Boolean.valueOf(subscriber != null); 296 } 297 298 return value; 299 } 300 catch (Exception e) 301 { 302 throw new UserPreferencesException("Error getting newsletter user preferences for login " + userIdentity + " and context " + storageContext, e); 303 } 304 } 305 306 @Override 307 public Double getUserPreferenceAsDouble(UserIdentity userIdentity, String storageContext, Map<String, String> contextVars, String id) throws UserPreferencesException 308 { 309 return null; 310 } 311 312 /** 313 * Create a subscriber object from the given input. 314 * @param siteName the site name. 315 * @param user the user. 316 * @param categoryId the category ID. 317 * @return the Subscriber object. 318 */ 319 protected Subscriber getSubscription(String siteName, User user, String categoryId) 320 { 321 return getSubscription(siteName, user, categoryId, true); 322 } 323 324 /** 325 * Create a subscriber object from the given input. 326 * @param siteName the site name. 327 * @param user the user. 328 * @param categoryId the category ID. 329 * @param generateDateAndToken true to generate a token and set the subscription date, false otherwise. 330 * @return the Subscriber object. 331 */ 332 protected Subscriber getSubscription(String siteName, User user, String categoryId, boolean generateDateAndToken) 333 { 334 Subscriber subscriber = new Subscriber(); 335 subscriber.setEmail(user.getEmail()); 336 subscriber.setSiteName(siteName); 337 subscriber.setCategoryId(categoryId); 338 339 if (generateDateAndToken) 340 { 341 subscriber.setSubscribedAt(new Date()); 342 343 // Generate unique token. 344 String token = UUID.randomUUID().toString(); 345 subscriber.setToken(token); 346 } 347 348 return subscriber; 349 } 350 351 /** 352 * Get the existing subscriptions for a user in a given site. 353 * @param email the user e-mail address. 354 * @param siteName the site name. 355 * @return a Set of category IDs, to which user has subscribed. 356 */ 357 protected Map<String, String> getExistingCategoryIds(String email, String siteName) 358 { 359 Map<String, String> existingCategoryIds = new HashMap<>(); 360 361 List<Subscriber> existingSubscriptions = _subscribersDao.getSubscriptions(email, siteName); 362 for (Subscriber subscription : existingSubscriptions) 363 { 364 existingCategoryIds.put(subscription.getCategoryId(), subscription.getToken()); 365 } 366 367 return existingCategoryIds; 368 } 369}