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.core.right; 017 018import java.util.ArrayList; 019import java.util.Collection; 020import java.util.Collections; 021import java.util.HashMap; 022import java.util.List; 023import java.util.Map; 024import java.util.Set; 025import java.util.stream.Collectors; 026 027import org.apache.avalon.framework.activity.Initializable; 028import org.apache.avalon.framework.service.ServiceException; 029import org.apache.avalon.framework.service.ServiceManager; 030import org.apache.commons.lang3.StringUtils; 031import org.apache.commons.lang3.Strings; 032import org.apache.ibatis.session.SqlSession; 033 034import org.ametys.core.ObservationConstants; 035import org.ametys.core.cache.AbstractCacheManager; 036import org.ametys.core.cache.Cache; 037import org.ametys.core.datasource.AbstractMyBatisDAO; 038import org.ametys.core.observation.Event; 039import org.ametys.core.observation.ObservationManager; 040import org.ametys.core.ui.Callable; 041import org.ametys.core.user.CurrentUserProvider; 042import org.ametys.runtime.i18n.I18nizableText; 043 044/** 045 * Manages registration of profiles 046 */ 047public class RightProfilesDAO extends AbstractMyBatisDAO implements Initializable 048{ 049 /** The component role. */ 050 public static final String ROLE = RightProfilesDAO.class.getName(); 051 052 /** profile cache id */ 053 private static final String PROFILES_CACHE = ROLE + "$profiles"; 054 055 /** profile rights cache id */ 056 private static final String PROFILE_RIGHTS_CACHE = ROLE + "$profileRigths"; 057 058 private ServiceManager _smanager; 059 060 private ObservationManager _observationManager; 061 062 private CurrentUserProvider _currentUserProvider; 063 064 private AbstractCacheManager _cacheManager; 065 066 067 @Override 068 public void service(ServiceManager smanager) throws ServiceException 069 { 070 _smanager = smanager; 071 super.service(smanager); 072 _cacheManager = (AbstractCacheManager) smanager.lookup(AbstractCacheManager.ROLE); 073 } 074 075 076 public void initialize() throws Exception 077 { 078 _cacheManager.createMemoryCache(PROFILES_CACHE, 079 new I18nizableText("plugin.core", "PLUGINS_CORE_RIGHT_PROFILES_CACHE_LABEL"), 080 new I18nizableText("plugin.core", "PLUGINS_CORE_RIGHT_PROFILES_CACHE_DESCRIPTION"), 081 true, 082 null); 083 _cacheManager.createMemoryCache(PROFILE_RIGHTS_CACHE, 084 new I18nizableText("plugin.core", "PLUGINS_CORE_RIGHT_PROFILE_RIGHTS_CACHE_LABEL"), 085 new I18nizableText("plugin.core", "PLUGINS_CORE_RIGHT_PROFILE_RIGHTS_CACHE_DESCRIPTION"), 086 true, 087 null); 088 } 089 090 /** 091 * Clear the profiles cache 092 */ 093 public synchronized void clearCache() 094 { 095 _getProfilesCache().invalidateAll(); 096 _getProfileRightsCache().invalidateAll(); 097 } 098 099 private synchronized Cache<String, Profile> _getProfilesCache() 100 { 101 Cache<String, Profile> cache = this._cacheManager.get(PROFILES_CACHE); 102 103 if (!cache.isInitialized()) 104 { 105 try (SqlSession session = getSession()) 106 { 107 Map<String, Profile> profilesMap = new HashMap<>(); 108 List<Profile> profiles = session.selectList("Profiles.getProfiles"); 109 profilesMap = profiles.stream().collect(Collectors.toMap(Profile::getId, p -> p)); 110 cache.putAll(profilesMap); 111 } 112 } 113 114 return cache; 115 } 116 117 private synchronized Cache<String, List<String>> _getProfileRightsCache() 118 { 119 120 Cache<String, List<String>> cache = this._cacheManager.get(PROFILE_RIGHTS_CACHE); 121 122 //As the cache can be cleared from admin tool, we add an entry to check is filled or not. 123 if (!cache.isInitialized()) 124 { 125 try (SqlSession session = getSession()) 126 { 127 128 Map<String, List<String>> profileRightsMap = new HashMap<>(); 129 List<Map<String, String>> rightAssociations = session.selectList("Profiles.getProfileRights"); 130 131 profileRightsMap = rightAssociations.stream() 132 .collect(Collectors.groupingBy( 133 rightAssociation -> rightAssociation.get("profileId"), 134 Collectors.mapping( 135 rightAssociation -> rightAssociation.get("rightId"), 136 Collectors.toList()))); 137 cache.putAll(profileRightsMap); 138 } 139 } 140 141 return cache; 142 } 143 144 /** 145 * Get all existing profiles 146 * @return The list for profiles 147 */ 148 public List<Profile> getProfiles() 149 { 150 Collection<Profile> profiles = _getProfilesCache().asMap().values(); 151 return new ArrayList<>(profiles); 152 } 153 154 /** 155 * Get the profiles on a given context 156 * @param context The context. Can be null. If null, the profiles with no context are returned. 157 * @return The list for profiles for this context 158 */ 159 public List<Profile> getProfiles(String context) 160 { 161 return _getProfilesCache().asMap().values().stream() 162 .filter(p -> Strings.CS.equals(p.getContext(), context)) 163 .collect(Collectors.toList()); 164 } 165 166 /** 167 * Get the list of profile and there associated rights 168 * @param context the profile context or null to retrieve only shared profiles 169 * @param includeReader true to include the reader profile 170 * @return the profiles 171 */ 172 @Callable(rights = Callable.NO_CHECK_REQUIRED) 173 public List<Map<String, Object>> getProfilesAsJson(String context, boolean includeReader) 174 { 175 List<Map<String, Object>> profilesAsJSON = new ArrayList<>(); 176 177 List<Profile> profiles = new ArrayList<>(); 178 profiles.addAll(getProfiles(null)); 179 180 if (StringUtils.isNotBlank(context)) 181 { 182 profiles.addAll(getProfiles(context)); 183 } 184 185 if (!includeReader) 186 { 187 Profile readerProfile = getProfile(RightManager.READER_PROFILE_ID); 188 profiles.remove(readerProfile); 189 } 190 191 for (Profile profile : profiles) 192 { 193 Map<String, Object> profile2json = profile.toJSON(); 194 profile2json.put("rights", getRights(profile)); 195 profilesAsJSON.add(profile2json); 196 } 197 198 return profilesAsJSON; 199 } 200 201 /** 202 * Get the reader profile 203 * @return the reader profile 204 */ 205 @Callable(rights = Callable.NO_CHECK_REQUIRED) 206 public Map<String, Object> getReaderProfileAsJson() 207 { 208 Profile readerProfile = getProfile(RightManager.READER_PROFILE_ID); 209 return readerProfile.toJSON(); 210 } 211 212 /** 213 * Get the profile with given identifier 214 * @param id The id of profile to retrieve 215 * @return The profile 216 */ 217 public Profile getProfile(String id) 218 { 219 return _getProfilesCache().get(id); 220 } 221 222 /** 223 * Get all profiles containing the right with given id 224 * @param rightId The id of right 225 * @return The id of profiles with this right 226 */ 227 public Set<String> getProfilesWithRight (String rightId) 228 { 229 return _getProfileRightsCache().asMap().entrySet().stream() 230 .filter(e -> e.getValue().contains(rightId)) 231 .map(e -> e.getKey()) 232 .collect(Collectors.toSet()); 233 } 234 235 /** 236 * Creates a new profile with null context. The identifier of the profile will be automatically generated from label. 237 * @param label The label of profile 238 * @return The create profile 239 */ 240 public Profile addProfile (String label) 241 { 242 return addProfile(label, null); 243 } 244 245 /** 246 * Creates a new profile. The identifier of the profile will be automatically generated from label. 247 * @param label The label of profile 248 * @param context The context. Can be null 249 * @return The create profile 250 */ 251 public Profile addProfile (String label, String context) 252 { 253 String id = _generateUniqueId(label); 254 Profile profile = new Profile(id, label, context); 255 addProfile(profile); 256 return profile; 257 } 258 259 private String _generateUniqueId(String label) 260 { 261 // Id generated from name lowercased, trimmed, and spaces and underscores replaced by dashes 262 String value = label.toLowerCase().trim().replaceAll("[\\W_]", "-").replaceAll("-+", "-").replaceAll("^-", ""); 263 int i = 2; 264 String suffixedValue = value; 265 while (getProfile(suffixedValue) != null) 266 { 267 suffixedValue = value + i; 268 i++; 269 } 270 271 return suffixedValue; 272 } 273 274 /** 275 * Creates a new profile 276 * @param id The unique identifier of profile 277 * @param label The label of profile 278 * @param context The context. Can be null 279 * @return The create profile 280 */ 281 public Profile addProfile (String id, String label, String context) 282 { 283 Profile profile = new Profile(id, label, context); 284 addProfile(profile); 285 return profile; 286 } 287 288 /** 289 * Add a new profile 290 * @param profile The profile to add 291 * @param silent Set to true to not notify observer of this update 292 */ 293 public void addProfile (Profile profile, boolean silent) 294 { 295 try (SqlSession session = getSession(true)) 296 { 297 session.insert("Profiles.addProfile", profile); 298 clearCache(); 299 if (!silent) 300 { 301 _notifyEvent(profile, ObservationConstants.EVENT_PROFILE_ADDED); 302 } 303 } 304 } 305 306 /** 307 * Add a new profile 308 * @param profile The profile to add 309 */ 310 public void addProfile (Profile profile) 311 { 312 addProfile(profile, false); 313 } 314 315 /** 316 * Rename a profile 317 * @param profile The profile to rename 318 * @param newLabel The updated label 319 */ 320 public void renameProfile (Profile profile, String newLabel) 321 { 322 renameProfile(profile, newLabel, false); 323 } 324 325 /** 326 * Rename a profile 327 * @param profile The profile to rename 328 * @param newLabel The updated label 329 * @param silent Set to true to not notify observer of this update 330 */ 331 public void renameProfile (Profile profile, String newLabel, boolean silent) 332 { 333 try (SqlSession session = getSession(true)) 334 { 335 Map<String, Object> params = new HashMap<>(); 336 params.put("id", profile.getId()); 337 params.put("label", newLabel); 338 session.update("Profiles.renameProfile", params); 339 340 clearCache(); 341 342 if (!silent) 343 { 344 _notifyEvent(profile, ObservationConstants.EVENT_PROFILE_UPDATED); 345 } 346 } 347 } 348 349 350 /** 351 * Get the rights of a profile 352 * @param profileId The profile id 353 * @return The rights 354 */ 355 public List<String> getRights (String profileId) 356 { 357 if (!StringUtils.isEmpty(profileId)) 358 { 359 List<String> list = _getProfileRightsCache().get(profileId); 360 if (list != null) 361 { 362 return list; 363 } 364 } 365 return Collections.EMPTY_LIST; 366 } 367 368 /** 369 * Get the rights of a profile 370 * @param profile The profile 371 * @return The rights 372 */ 373 public List<String> getRights (Profile profile) 374 { 375 if (profile == null) 376 { 377 return Collections.EMPTY_LIST; 378 } 379 else 380 { 381 return getRights(profile.getId()); 382 } 383 } 384 385 /** 386 * Add a right to a profile 387 * @param profile The profile 388 * @param rightId The id of right to add 389 */ 390 public void addRight (Profile profile, String rightId) 391 { 392 try (SqlSession session = getSession(true)) 393 { 394 _addRight (session, profile, rightId); 395 } 396 397 clearCache(); 398 } 399 400 /** 401 * Add a right to a profile 402 * @param profile The profile 403 * @param rightIds The id of rights to add 404 */ 405 public void addRights (Profile profile, List<String> rightIds) 406 { 407 try (SqlSession session = getSession()) 408 { 409 for (String rightId : rightIds) 410 { 411 _addRight (session, profile, rightId); 412 } 413 414 session.commit(); 415 } 416 417 clearCache(); 418 } 419 420 /** 421 * Update the rights of a profile 422 * @param profile The profile 423 * @param rights The rights of the profile 424 */ 425 public void updateRights (Profile profile, List<String> rights) 426 { 427 updateRights(profile, rights, false); 428 } 429 430 /** 431 * Update the rights of a profile 432 * @param profile The profile 433 * @param rights The rights of the profile 434 * @param silent Set to true to not notify observer of this update 435 */ 436 public void updateRights (Profile profile, List<String> rights, boolean silent) 437 { 438 try (SqlSession session = getSession()) 439 { 440 session.delete("Profiles.deleteProfileRights", profile.getId()); 441 442 if (rights != null) 443 { 444 for (String rightId : rights) 445 { 446 _addRight (session, profile, rightId); 447 } 448 } 449 450 session.commit(); 451 452 clearCache(); 453 454 if (!silent) 455 { 456 _notifyEvent(profile, ObservationConstants.EVENT_PROFILE_UPDATED); 457 } 458 } 459 } 460 461 private void _addRight (SqlSession session, Profile profile, String rightId) 462 { 463 Map<String, Object> params = new HashMap<>(); 464 params.put("profileId", profile.getId()); 465 params.put("rightId", rightId); 466 467 session.insert("Profiles.addRight", params); 468 } 469 470 /** 471 * Add a right to a profile 472 * @param profile The profile 473 */ 474 public void removeRights (Profile profile) 475 { 476 removeRights(profile, false); 477 } 478 479 /** 480 * Add a right to a profile 481 * @param profile The profile 482 * @param silent Set to true to not notify observer of this update 483 */ 484 public void removeRights (Profile profile, boolean silent) 485 { 486 try (SqlSession session = getSession(true)) 487 { 488 session.delete("Profiles.deleteProfileRights", profile.getId()); 489 490 clearCache(); 491 492 if (!silent) 493 { 494 _notifyEvent(profile, ObservationConstants.EVENT_PROFILE_UPDATED); 495 } 496 } 497 } 498 499 /** 500 * Remove a right from a profile 501 * @param profile The profile 502 * @param rightId The id of right to add 503 */ 504 public void removeRight (Profile profile, String rightId) 505 { 506 removeRight(profile, rightId, false); 507 } 508 509 /** 510 * Remove a right from a profile 511 * @param profile The profile 512 * @param rightId The id of right to add 513 * @param silent Set to true to not notify observer of this update 514 */ 515 public void removeRight (Profile profile, String rightId, boolean silent) 516 { 517 try (SqlSession session = getSession(true)) 518 { 519 Map<String, Object> params = new HashMap<>(); 520 params.put("profileId", profile.getId()); 521 params.put("rightId", rightId); 522 session.delete("Profiles.deleteProfileRight", params); 523 524 clearCache(); 525 526 if (!silent) 527 { 528 _notifyEvent(profile, ObservationConstants.EVENT_PROFILE_UPDATED); 529 } 530 } 531 532 clearCache(); 533 } 534 535 /** 536 * Delete a profile 537 * @param profile The profile to delete 538 */ 539 public void deleteProfile (Profile profile) 540 { 541 deleteProfile(profile, false); 542 } 543 544 /** 545 * Delete a profile 546 * @param profile The profile to delete 547 * @param silent Set to true to not notify observer of this update 548 */ 549 public void deleteProfile (Profile profile, boolean silent) 550 { 551 try (SqlSession session = getSession()) 552 { 553 session.delete("Profiles.deleteProfile", profile.getId()); 554 session.delete("Profiles.deleteProfileRights", profile.getId()); 555 556 session.commit(); 557 558 clearCache(); 559 560 if (!silent) 561 { 562 _notifyEvent(profile, ObservationConstants.EVENT_PROFILE_DELETED); 563 } 564 } 565 } 566 567 private void _notifyEvent (Profile profile, String eventId) 568 { 569 try 570 { 571 if (_observationManager == null) 572 { 573 _observationManager = (ObservationManager) _smanager.lookup(ObservationManager.ROLE); 574 } 575 if (_currentUserProvider == null) 576 { 577 _currentUserProvider = (CurrentUserProvider) _smanager.lookup(CurrentUserProvider.ROLE); 578 } 579 580 Map<String, Object> eventParams = new HashMap<>(); 581 eventParams.put(ObservationConstants.ARGS_PROFILE, profile); 582 _observationManager.notify(new Event(eventId, _currentUserProvider.getUser(), eventParams)); 583 } 584 catch (ServiceException e) 585 { 586 getLogger().error("Fail to notify observers for event '" + eventId + "'", e); 587 } 588 } 589}