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