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.plugins.core.impl.group.directory.jdbc; 017 018import java.sql.Connection; 019import java.sql.PreparedStatement; 020import java.sql.ResultSet; 021import java.sql.SQLException; 022import java.sql.Statement; 023import java.util.ArrayList; 024import java.util.Arrays; 025import java.util.Collection; 026import java.util.Collections; 027import java.util.HashMap; 028import java.util.HashSet; 029import java.util.Iterator; 030import java.util.LinkedHashSet; 031import java.util.List; 032import java.util.Map; 033import java.util.Set; 034 035import org.apache.avalon.framework.activity.Disposable; 036import org.apache.avalon.framework.service.ServiceException; 037import org.apache.avalon.framework.service.ServiceManager; 038import org.apache.avalon.framework.service.Serviceable; 039import org.apache.commons.lang3.ArrayUtils; 040import org.apache.commons.lang3.StringUtils; 041import org.apache.excalibur.source.SourceResolver; 042 043import org.ametys.core.ObservationConstants; 044import org.ametys.core.cache.AbstractCacheManager; 045import org.ametys.core.cache.Cache; 046import org.ametys.core.datasource.ConnectionHelper; 047import org.ametys.core.group.Group; 048import org.ametys.core.group.GroupIdentity; 049import org.ametys.core.group.InvalidModificationException; 050import org.ametys.core.group.ModifiableGroup; 051import org.ametys.core.group.directory.GroupDirectory; 052import org.ametys.core.group.directory.GroupDirectoryModel; 053import org.ametys.core.group.directory.ModifiableGroupDirectory; 054import org.ametys.core.observation.Event; 055import org.ametys.core.observation.ObservationManager; 056import org.ametys.core.script.SQLScriptHelper; 057import org.ametys.core.user.CurrentUserProvider; 058import org.ametys.core.user.UserIdentity; 059import org.ametys.core.util.Cacheable; 060import org.ametys.runtime.i18n.I18nizableText; 061import org.ametys.runtime.plugin.component.AbstractLogEnabled; 062 063/** 064 * Standard implementation of {@link GroupDirectory} from the core database. 065 */ 066public class JdbcGroupDirectory extends AbstractLogEnabled implements ModifiableGroupDirectory, Serviceable, Cacheable, Disposable 067{ 068 /** Name of the parameter holding the datasource id */ 069 private static final String __DATASOURCE_PARAM_NAME = "runtime.groups.jdbc.datasource"; 070 /** Name of the parameter holding the SQL table name for storing the groups */ 071 private static final String __GROUPS_LIST_TABLE_PARAM_NAME = "runtime.groups.jdbc.list.table"; 072 /** Name of the parameter holding the SQL table name for storing the composition (i.e. users) of the groups */ 073 private static final String __GROUPS_COMPOSITION_TABLE_PARAM_NAME = "runtime.groups.jdbc.composition.table"; 074 075 private static final String __GROUPS_LIST_COLUMN_ID = "Id"; 076 private static final String __GROUPS_LIST_COLUMN_LABEL = "Label"; 077 private static final String __GROUPS_COMPOSITION_COLUMN_GROUPID = "Group_Id"; 078 private static final String __GROUPS_COMPOSITION_COLUMN_LOGIN = "Login"; 079 private static final String __GROUPS_COMPOSITION_COLUMN_POPULATIONID = "UserPopulation_Id"; 080 081 private static final String __JDBC_GROUPDIRECTORY_GROUPS_BY_USER_CACHE_NAME_PREFIX = JdbcGroupDirectory.class.getName() + "$groups.by.user$"; 082 083 /** The observation manager */ 084 protected ObservationManager _observationManager; 085 /** The current user provider */ 086 protected CurrentUserProvider _currentUserProvider; 087 /** The cocoon source resolver */ 088 protected SourceResolver _sourceResolver; 089 /** The cache manager */ 090 protected AbstractCacheManager _cacheManager; 091 092 /** The identifier of data source */ 093 protected String _dataSourceId; 094 /** The name of the SQL table storing the groups */ 095 protected String _groupsListTableName; 096 /** The name of the SQL table storing the composition (i.e. users) of the groups*/ 097 protected String _groupsCompositionTableName; 098 099 /** The id */ 100 protected String _id; 101 /** The label */ 102 protected I18nizableText _label; 103 /** The id of the {@link GroupDirectoryModel} */ 104 private String _groupDirectoryModelId; 105 /** The map of the values of the parameters */ 106 private Map<String, Object> _paramValues; 107 private boolean _lazyInitialized; 108 109 // Cannot use _id as two GroupDirectories with same id can co-exist during a short amount of time (during GroupDirectoryDAO#_read) 110 private final String _uniqueCacheSuffix = org.ametys.core.util.StringUtils.generateKey(); 111 112 @Override 113 public void service(ServiceManager manager) throws ServiceException 114 { 115 _observationManager = (ObservationManager) manager.lookup(ObservationManager.ROLE); 116 _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE); 117 _sourceResolver = (SourceResolver) manager.lookup(SourceResolver.ROLE); 118 _cacheManager = (AbstractCacheManager) manager.lookup(AbstractCacheManager.ROLE); 119 } 120 121 @Override 122 public String getId() 123 { 124 return _id; 125 } 126 127 @Override 128 public I18nizableText getLabel() 129 { 130 return _label; 131 } 132 133 @Override 134 public void setId(String id) 135 { 136 _id = id; 137 } 138 139 @Override 140 public void setLabel(I18nizableText label) 141 { 142 _label = label; 143 } 144 145 @Override 146 public String getGroupDirectoryModelId () 147 { 148 return _groupDirectoryModelId; 149 } 150 151 @Override 152 public Map<String, Object> getParameterValues() 153 { 154 return _paramValues; 155 } 156 157 @Override 158 public void dispose() 159 { 160 removeCaches(); 161 } 162 163 @Override 164 public Collection<SingleCacheConfiguration> getManagedCaches() 165 { 166 return Arrays.asList( 167 SingleCacheConfiguration.of( 168 __JDBC_GROUPDIRECTORY_GROUPS_BY_USER_CACHE_NAME_PREFIX + _uniqueCacheSuffix, 169 _buildI18n("PLUGINS_CORE_GROUPS_JDBC_CACHE_GROUPS_BY_USER_LABEL"), 170 _buildI18n("PLUGINS_CORE_GROUPS_JDBC_CACHE_GROUPS_BY_USER_DESC")) 171 ); 172 } 173 174 @Override 175 public boolean hasComputableSize() 176 { 177 return true; 178 } 179 180 private I18nizableText _buildI18n(String i18Key) 181 { 182 String catalogue = "plugin.core-impl"; 183 I18nizableText groupDirectoryId = new I18nizableText(getId()); 184 Map<String, I18nizableText> params = Map.of( 185 "id", groupDirectoryId); 186 return new I18nizableText(catalogue, i18Key, params); 187 } 188 189 private Cache<UserIdentity, Set<String>> _getCacheGroupsByUser() 190 { 191 return getCacheManager().get(__JDBC_GROUPDIRECTORY_GROUPS_BY_USER_CACHE_NAME_PREFIX + _uniqueCacheSuffix); 192 } 193 194 @Override 195 public AbstractCacheManager getCacheManager() 196 { 197 return _cacheManager; 198 } 199 200 @Override 201 public void init(String groupDirectoryModelId, Map<String, Object> paramValues) 202 { 203 _groupDirectoryModelId = groupDirectoryModelId; 204 _paramValues = paramValues; 205 206 _groupsListTableName = (String) paramValues.get(__GROUPS_LIST_TABLE_PARAM_NAME); 207 _groupsCompositionTableName = (String) paramValues.get(__GROUPS_COMPOSITION_TABLE_PARAM_NAME); 208 _dataSourceId = (String) paramValues.get(__DATASOURCE_PARAM_NAME); 209 210 createCaches(); 211 } 212 213 /** 214 * Get the connection to the database 215 * @return the SQL connection 216 */ 217 @SuppressWarnings("unchecked") 218 protected Connection getSQLConnection() 219 { 220 Connection connection = ConnectionHelper.getConnection(_dataSourceId); 221 222 if (!_lazyInitialized) 223 { 224 try 225 { 226 SQLScriptHelper.createTableIfNotExists(connection, _groupsListTableName, "plugin:core://scripts/%s/jdbc_groups.template.sql", _sourceResolver, 227 (Map) ArrayUtils.toMap(new String[][] {{"%TABLENAME%", _groupsListTableName}, {"%TABLENAME_COMPOSITION%", _groupsCompositionTableName}})); 228 } 229 catch (Exception e) 230 { 231 getLogger().error("The tables requires by the " + this.getClass().getName() + " could not be created. A degraded behavior will occur", e); 232 } 233 234 _lazyInitialized = true; 235 } 236 237 return connection; 238 } 239 240 @Override 241 public ModifiableGroup getGroup(String groupID) 242 { 243 JdbcGroup group = null; 244 245 Connection connection = null; 246 PreparedStatement stmt = null; 247 ResultSet rs = null; 248 249 try 250 { 251 connection = getSQLConnection(); 252 253 String sql = "SELECT " + __GROUPS_LIST_COLUMN_LABEL + " FROM " + _groupsListTableName + " WHERE " + __GROUPS_LIST_COLUMN_ID + " = ?"; 254 stmt = connection.prepareStatement(sql); 255 stmt.setInt(1, Integer.parseInt(groupID)); 256 257 if (getLogger().isDebugEnabled()) 258 { 259 getLogger().debug(sql); 260 } 261 rs = stmt.executeQuery(); 262 263 // Iterate over all the groups 264 if (rs.next()) 265 { 266 String label = rs.getString(__GROUPS_LIST_COLUMN_LABEL); 267 group = new JdbcGroup(new GroupIdentity(groupID, getId()), label, this); 268 269 _fillGroup(group, connection); 270 } 271 } 272 catch (NumberFormatException e) 273 { 274 getLogger().error("Group ID must be an integer.", e); 275 return null; 276 } 277 catch (SQLException e) 278 { 279 getLogger().error("Error communication with database", e); 280 return null; 281 } 282 finally 283 { 284 ConnectionHelper.cleanup(rs); 285 ConnectionHelper.cleanup(stmt); 286 ConnectionHelper.cleanup(connection); 287 } 288 289 // Return the found group or null 290 return group; 291 } 292 293 @Override 294 public Set<Group> getGroups() 295 { 296 Set<Group> groups = new LinkedHashSet<>(); 297 298 Connection connection = null; 299 Statement stmt = null; 300 ResultSet rs = null; 301 302 try 303 { 304 connection = getSQLConnection(); 305 306 stmt = connection.createStatement(); 307 String sql = _createGetGroupsClause(); 308 309 if (getLogger().isDebugEnabled()) 310 { 311 getLogger().debug(sql); 312 } 313 rs = stmt.executeQuery(sql); 314 315 // Iterate over all the groups 316 while (rs.next()) 317 { 318 String groupID = rs.getString(__GROUPS_LIST_COLUMN_ID); 319 String label = rs.getString(__GROUPS_LIST_COLUMN_LABEL); 320 JdbcGroup group = new JdbcGroup(new GroupIdentity(groupID, getId()), label, this); 321 322 _fillGroup(group, connection); 323 324 // Add current group 325 groups.add(group); 326 } 327 } 328 catch (SQLException e) 329 { 330 getLogger().error("Error communication with database", e); 331 return Collections.emptySet(); 332 } 333 finally 334 { 335 ConnectionHelper.cleanup(rs); 336 ConnectionHelper.cleanup(stmt); 337 ConnectionHelper.cleanup(connection); 338 } 339 340 return groups; 341 } 342 343 /** 344 * Get the sql clause that gets all groups 345 * @return A non null sql clause (e.g. "select ... from ... where ...") 346 */ 347 protected String _createGetGroupsClause() 348 { 349 return "SELECT " + __GROUPS_LIST_COLUMN_ID + ", " + __GROUPS_LIST_COLUMN_LABEL + " FROM " + _groupsListTableName + " ORDER BY " + __GROUPS_LIST_COLUMN_LABEL; 350 } 351 352 /** 353 * Fill users set in a group. 354 * 355 * @param group The group to fill. 356 * @param connection The SQL connection. 357 * @throws SQLException If a problem occurs. 358 */ 359 protected void _fillGroup(JdbcGroup group, Connection connection) throws SQLException 360 { 361 PreparedStatement stmt = null; 362 ResultSet rs = null; 363 364 try 365 { 366 String sql = "SELECT " + __GROUPS_COMPOSITION_COLUMN_LOGIN + ", " + __GROUPS_COMPOSITION_COLUMN_POPULATIONID + " FROM " + _groupsCompositionTableName + " WHERE " + __GROUPS_COMPOSITION_COLUMN_GROUPID + " = ?"; 367 368 stmt = connection.prepareStatement(sql); 369 370 stmt.setInt(1, Integer.parseInt(group.getIdentity().getId())); 371 372 if (getLogger().isDebugEnabled()) 373 { 374 getLogger().debug(sql); 375 } 376 rs = stmt.executeQuery(); 377 378 // Iterate over all the users from current group 379 while (rs.next()) 380 { 381 UserIdentity identity = new UserIdentity(rs.getString(__GROUPS_COMPOSITION_COLUMN_LOGIN), rs.getString(__GROUPS_COMPOSITION_COLUMN_POPULATIONID)); 382 group.addUser(identity); 383 } 384 } 385 finally 386 { 387 ConnectionHelper.cleanup(rs); 388 ConnectionHelper.cleanup(stmt); 389 } 390 } 391 392 @Override 393 public Set<String> getUserGroups(UserIdentity userIdentity) 394 { 395 if (isCachingEnabled() && _getCacheGroupsByUser().hasKey(userIdentity)) 396 { 397 Set<String> userGroups = _getCacheGroupsByUser().get(userIdentity); 398 // Cache hit, return the results. 399 return userGroups; 400 } 401 402 Set<String> userGroups = _executeSqlForUserGroups(userIdentity); 403 404 // Cache the results. 405 if (isCachingEnabled()) 406 { 407 _getCacheGroupsByUser().put(userIdentity, userGroups); 408 } 409 410 return userGroups; 411 } 412 413 private Set<String> _executeSqlForUserGroups(UserIdentity userIdentity) 414 { 415 String login = userIdentity.getLogin(); 416 String populationId = userIdentity.getPopulationId(); 417 418 Set<String> groups = new HashSet<>(); 419 if (login == null) 420 { 421 return groups; 422 } 423 424 Connection connection = null; 425 PreparedStatement stmt = null; 426 ResultSet rs = null; 427 428 try 429 { 430 connection = getSQLConnection(); 431 432 String sql = "SELECT " + __GROUPS_COMPOSITION_COLUMN_GROUPID + " FROM " + _groupsCompositionTableName + " WHERE " + __GROUPS_COMPOSITION_COLUMN_LOGIN + " = ? AND " + __GROUPS_COMPOSITION_COLUMN_POPULATIONID + " = ?"; 433 stmt = connection.prepareStatement(sql); 434 stmt.setString(1, login); 435 stmt.setString(2, populationId); 436 437 if (getLogger().isDebugEnabled()) 438 { 439 getLogger().debug(sql); 440 } 441 rs = stmt.executeQuery(); 442 443 // Iterate over all the groups 444 while (rs.next()) 445 { 446 String groupID = rs.getString(__GROUPS_COMPOSITION_COLUMN_GROUPID); 447 448 // Add the current group 449 groups.add(groupID); 450 } 451 } 452 catch (SQLException e) 453 { 454 getLogger().error("Error communication with database", e); 455 return Collections.emptySet(); 456 } 457 finally 458 { 459 ConnectionHelper.cleanup(rs); 460 ConnectionHelper.cleanup(stmt); 461 ConnectionHelper.cleanup(connection); 462 } 463 464 // Return the groups, potentially empty 465 return groups; 466 } 467 468 @Override 469 public List<Group> getGroups(int count, int offset, Map parameters) 470 { 471 List<Group> groups = new ArrayList<>(); 472 473 String pattern = (String) parameters.get("pattern"); 474 475 Iterator iterator = getGroups().iterator(); 476 477 //int totalCount = 0; 478 int currentOffset = offset; 479 480 while (currentOffset > 0 && iterator.hasNext()) 481 { 482 Group group = (Group) iterator.next(); 483 if (StringUtils.isEmpty(pattern) || group.getLabel().toLowerCase().indexOf(pattern.toLowerCase()) != -1 || (group.getIdentity() != null && group.getIdentity().getId().toLowerCase().indexOf(pattern.toLowerCase()) != -1)) 484 { 485 currentOffset--; 486 //totalCount++; 487 } 488 } 489 490 int currentCount = count; 491 while ((count == -1 || currentCount > 0) && iterator.hasNext()) 492 { 493 Group group = (Group) iterator.next(); 494 495 if (StringUtils.isEmpty(pattern) || group.getLabel().toLowerCase().indexOf(pattern.toLowerCase()) != -1 || (group.getIdentity() != null && group.getIdentity().getId().toLowerCase().indexOf(pattern.toLowerCase()) != -1)) 496 { 497 groups.add(group); 498 currentCount--; 499 //totalCount++; 500 } 501 } 502 503 /*while (iterator.hasNext()) 504 { 505 Group group = (Group) iterator.next(); 506 507 if (StringUtils.isEmpty(pattern) || group.getLabel().toLowerCase().indexOf(pattern.toLowerCase()) != -1) 508 { 509 totalCount++; 510 } 511 }*/ 512 513 // TODO return totalCount 514 return groups; 515 } 516 517 @Override 518 public ModifiableGroup add(String name) throws InvalidModificationException 519 { 520 Connection connection = null; 521 PreparedStatement statement = null; 522 ResultSet rs = null; 523 524 String id = null; 525 526 try 527 { 528 connection = getSQLConnection(); 529 String dbType = ConnectionHelper.getDatabaseType(connection); 530 531 if (ConnectionHelper.DATABASE_ORACLE.equals(dbType)) 532 { 533 statement = connection.prepareStatement("SELECT seq_" + _groupsListTableName + ".nextval FROM dual"); 534 rs = statement.executeQuery(); 535 if (rs.next()) 536 { 537 id = rs.getString(1); 538 } 539 ConnectionHelper.cleanup(rs); 540 ConnectionHelper.cleanup(statement); 541 542 statement = connection.prepareStatement("INSERT INTO " + _groupsListTableName + " (Id, Label) VALUES(?, ?)"); 543 statement.setString(1, id); 544 statement.setString(2, name); 545 } 546 else 547 { 548 statement = connection.prepareStatement("INSERT INTO " + _groupsListTableName + " (" + __GROUPS_LIST_COLUMN_LABEL + ") VALUES (?)"); 549 statement.setString(1, name); 550 } 551 552 statement.executeUpdate(); 553 554 ConnectionHelper.cleanup(statement); 555 556 //FIXME Write query working with all database 557 if (ConnectionHelper.DATABASE_MYSQL.equals(dbType)) 558 { 559 statement = connection.prepareStatement("SELECT " + __GROUPS_LIST_COLUMN_ID + " FROM " + _groupsListTableName + " WHERE " + __GROUPS_LIST_COLUMN_ID + " = last_insert_id()"); 560 rs = statement.executeQuery(); 561 if (rs.next()) 562 { 563 id = rs.getString(__GROUPS_LIST_COLUMN_ID); 564 } 565 else 566 { 567 if (connection.getAutoCommit()) 568 { 569 throw new InvalidModificationException("Cannot retrieve inserted group. Group was created but listeners not called : base may be inconsistant"); 570 } 571 else 572 { 573 connection.rollback(); 574 throw new InvalidModificationException("Cannot retrieve inserted group. Rolling back"); 575 } 576 } 577 } 578 else if (ConnectionHelper.DATABASE_DERBY.equals(dbType)) 579 { 580 statement = connection.prepareStatement("VALUES IDENTITY_VAL_LOCAL ()"); 581 rs = statement.executeQuery(); 582 if (rs.next()) 583 { 584 id = rs.getString(1); 585 } 586 } 587 else if (ConnectionHelper.DATABASE_HSQLDB.equals(dbType)) 588 { 589 statement = connection.prepareStatement("CALL IDENTITY ()"); 590 rs = statement.executeQuery(); 591 if (rs.next()) 592 { 593 id = rs.getString(1); 594 } 595 } 596 else if (ConnectionHelper.DATABASE_POSTGRES.equals(dbType)) 597 { 598 statement = connection.prepareStatement("SELECT currval('groups_id_seq')"); 599 rs = statement.executeQuery(); 600 if (rs.next()) 601 { 602 id = rs.getString(1); 603 } 604 } 605 606 if (id != null) 607 { 608 Map<String, Object> eventParams = new HashMap<>(); 609 eventParams.put(ObservationConstants.ARGS_GROUP, new GroupIdentity(id, getId())); 610 _observationManager.notify(new Event(ObservationConstants.EVENT_GROUP_ADDED, _currentUserProvider.getUser(), eventParams)); 611 } 612 } 613 catch (SQLException ex) 614 { 615 throw new RuntimeException(ex); 616 } 617 finally 618 { 619 ConnectionHelper.cleanup(rs); 620 ConnectionHelper.cleanup(statement); 621 ConnectionHelper.cleanup(connection); 622 } 623 624 return new JdbcGroup(new GroupIdentity(id, getId()), name, this); 625 } 626 627 @Override 628 public void update(ModifiableGroup userGroup) throws InvalidModificationException 629 { 630 Connection connection = null; 631 PreparedStatement statement = null; 632 633 if (getLogger().isDebugEnabled()) 634 { 635 getLogger().debug("Updating group " + GroupIdentity.groupIdentityToString(userGroup.getIdentity()) + " with " + userGroup.getUsers().size() + " user(s)"); 636 } 637 638 try 639 { 640 641 connection = getSQLConnection(); 642 643 // Start transaction. 644 connection.setAutoCommit(false); 645 646 statement = connection.prepareStatement("UPDATE " + _groupsListTableName + " SET " + __GROUPS_LIST_COLUMN_LABEL + "=? WHERE " + __GROUPS_LIST_COLUMN_ID + " = ?"); 647 statement.setString(1, userGroup.getLabel()); 648 statement.setInt(2, Integer.parseInt(userGroup.getIdentity().getId())); 649 650 if (statement.executeUpdate() == 0) 651 { 652 throw new InvalidModificationException("No group with id '" + userGroup.getIdentity().getId() + "' may be removed"); 653 } 654 ConnectionHelper.cleanup(statement); 655 656 statement = connection.prepareStatement("DELETE FROM " + _groupsCompositionTableName + " WHERE " + __GROUPS_COMPOSITION_COLUMN_GROUPID + " = ?"); 657 statement.setInt(1, Integer.parseInt(userGroup.getIdentity().getId())); 658 659 statement.executeUpdate(); 660 ConnectionHelper.cleanup(statement); 661 662 if (!userGroup.getUsers().isEmpty()) 663 { 664 // Tests if the connection supports batch updates. 665 boolean supportsBatch = connection.getMetaData().supportsBatchUpdates(); 666 667 statement = connection.prepareStatement("INSERT INTO " + _groupsCompositionTableName + " (" + __GROUPS_COMPOSITION_COLUMN_GROUPID + ", " + __GROUPS_COMPOSITION_COLUMN_LOGIN + ", " + __GROUPS_COMPOSITION_COLUMN_POPULATIONID + ") VALUES (?, ?, ?)"); 668 669 for (UserIdentity identity : userGroup.getUsers()) 670 { 671 String login = identity.getLogin(); 672 String populationId = identity.getPopulationId(); 673 statement.setInt(1, Integer.parseInt(userGroup.getIdentity().getId())); 674 statement.setString(2, login); 675 statement.setString(3, populationId); 676 677 // If batch updates are supported, add to the batch, else execute directly. 678 if (supportsBatch) 679 { 680 statement.addBatch(); 681 } 682 else 683 { 684 statement.executeUpdate(); 685 } 686 } 687 688 // If the insert queries were queued in a batch, execute it. 689 if (supportsBatch) 690 { 691 statement.executeBatch(); 692 } 693 } 694 695 ConnectionHelper.cleanup(statement); 696 697 // Commit transaction. 698 connection.commit(); 699 700 // Clear global cache 701 _getCacheGroupsByUser().invalidateAll(); 702 703 Map<String, Object> eventParams = new HashMap<>(); 704 eventParams.put(ObservationConstants.ARGS_GROUP, userGroup.getIdentity()); 705 _observationManager.notify(new Event(ObservationConstants.EVENT_GROUP_UPDATED, _currentUserProvider.getUser(), eventParams)); 706 } 707 catch (NumberFormatException ex) 708 { 709 throw new InvalidModificationException("No group with id '" + userGroup.getIdentity().getId() + "' may be removed", ex); 710 } 711 catch (SQLException ex) 712 { 713 throw new RuntimeException(ex); 714 } 715 finally 716 { 717 ConnectionHelper.cleanup(statement); 718 ConnectionHelper.cleanup(connection); 719 } 720 721 if (getLogger().isDebugEnabled()) 722 { 723 getLogger().debug("Updated group " + GroupIdentity.groupIdentityToString(userGroup.getIdentity()) + " with " + userGroup.getUsers().size() + " user(s)"); 724 } 725 } 726 727 @Override 728 public void remove(String groupID) throws InvalidModificationException 729 { 730 Connection connection = null; 731 PreparedStatement statement = null; 732 733 try 734 { 735 connection = getSQLConnection(); 736 737 statement = connection.prepareStatement("DELETE FROM " + _groupsListTableName + " WHERE " + __GROUPS_LIST_COLUMN_ID + " = ?"); 738 statement.setInt(1, Integer.parseInt(groupID)); 739 740 if (statement.executeUpdate() == 0) 741 { 742 throw new InvalidModificationException("No group with id '" + groupID + "' may be removed"); 743 } 744 ConnectionHelper.cleanup(statement); 745 746 statement = connection.prepareStatement("DELETE FROM " + _groupsCompositionTableName + " WHERE " + __GROUPS_COMPOSITION_COLUMN_GROUPID + " = ?"); 747 statement.setInt(1, Integer.parseInt(groupID)); 748 749 statement.executeUpdate(); 750 751 // Clear global cache 752 _getCacheGroupsByUser().invalidateAll(); 753 754 Map<String, Object> eventParams = new HashMap<>(); 755 eventParams.put(ObservationConstants.ARGS_GROUP, new GroupIdentity(groupID, getId())); 756 _observationManager.notify(new Event(ObservationConstants.EVENT_GROUP_DELETED, _currentUserProvider.getUser(), eventParams)); 757 } 758 catch (NumberFormatException ex) 759 { 760 throw new InvalidModificationException("No group with id '" + groupID + "' may be removed, the ID must be a number.", ex); 761 } 762 catch (SQLException ex) 763 { 764 throw new RuntimeException(ex); 765 } 766 finally 767 { 768 ConnectionHelper.cleanup(statement); 769 ConnectionHelper.cleanup(connection); 770 } 771 } 772 773 private static final class JdbcGroup implements ModifiableGroup 774 { 775 private Set<UserIdentity> _users; 776 private GroupIdentity _identity; 777 private String _groupLabel; 778 private GroupDirectory _groupDirectory; 779 780 JdbcGroup(GroupIdentity identity, String label, GroupDirectory groupDirectory) 781 { 782 _identity = identity; 783 _groupLabel = label; 784 _groupDirectory = groupDirectory; 785 _users = new HashSet<>(); 786 } 787 788 @Override 789 public GroupIdentity getIdentity() 790 { 791 return _identity; 792 } 793 794 @Override 795 public String getLabel() 796 { 797 return _groupLabel; 798 } 799 800 @Override 801 public GroupDirectory getGroupDirectory() 802 { 803 return _groupDirectory; 804 } 805 806 @Override 807 public void setLabel(String label) 808 { 809 _groupLabel = label; 810 } 811 812 @Override 813 public void addUser(UserIdentity user) 814 { 815 _users.add(user); 816 } 817 818 @Override 819 public void removeUser(UserIdentity user) 820 { 821 _users.remove(user); 822 } 823 824 @Override 825 public void removeUsers() 826 { 827 _users.clear(); 828 } 829 830 @Override 831 public Set<UserIdentity> getUsers() 832 { 833 return _users; 834 } 835 836 @Override 837 public String toString() 838 { 839 StringBuffer sb = new StringBuffer("UserGroup["); 840 sb.append(_identity); 841 sb.append(" ("); 842 sb.append(_groupLabel); 843 sb.append(") => "); 844 sb.append(_users.toString()); 845 sb.append("]"); 846 return sb.toString(); 847 } 848 849 @Override 850 public boolean equals(Object another) 851 { 852 if (another == null || !(another instanceof JdbcGroup)) 853 { 854 return false; 855 } 856 857 JdbcGroup otherGroup = (JdbcGroup) another; 858 859 return _identity != null && _identity.equals(otherGroup.getIdentity()); 860 } 861 862 @Override 863 public int hashCode() 864 { 865 return _identity.hashCode(); 866 } 867 } 868}