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.user.directory; 017 018import java.sql.Connection; 019import java.sql.PreparedStatement; 020import java.sql.ResultSet; 021import java.sql.SQLException; 022import java.util.ArrayList; 023import java.util.Arrays; 024import java.util.Collection; 025import java.util.Collections; 026import java.util.HashMap; 027import java.util.LinkedHashMap; 028import java.util.List; 029import java.util.Map; 030 031import org.apache.avalon.framework.activity.Disposable; 032import org.apache.avalon.framework.component.Component; 033import org.apache.avalon.framework.context.Context; 034import org.apache.avalon.framework.context.ContextException; 035import org.apache.avalon.framework.context.Contextualizable; 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.codec.digest.DigestUtils; 040import org.apache.commons.lang.RandomStringUtils; 041import org.apache.commons.lang.StringUtils; 042import org.apache.commons.lang3.ArrayUtils; 043import org.apache.excalibur.source.SourceResolver; 044 045import org.ametys.core.ObservationConstants; 046import org.ametys.core.cache.AbstractCacheManager; 047import org.ametys.core.cache.Cache; 048import org.ametys.core.datasource.ConnectionHelper; 049import org.ametys.core.migration.MigrationExtensionPoint; 050import org.ametys.core.migration.storage.VersionStorageExtensionPoint; 051import org.ametys.core.migration.storage.impl.SqlVersionStorage; 052import org.ametys.core.observation.Event; 053import org.ametys.core.observation.ObservationManager; 054import org.ametys.core.script.SQLScriptHelper; 055import org.ametys.core.user.CurrentUserProvider; 056import org.ametys.core.user.InvalidModificationException; 057import org.ametys.core.user.User; 058import org.ametys.core.user.UserIdentity; 059import org.ametys.core.user.directory.ModifiableUserDirectory; 060import org.ametys.core.user.directory.NotUniqueUserException; 061import org.ametys.core.util.Cacheable; 062import org.ametys.core.util.mail.SendMailHelper; 063import org.ametys.plugins.core.jdbc.JdbcParameterTypeExtensionPoint; 064import org.ametys.runtime.i18n.I18nizableTextParameter; 065import org.ametys.runtime.i18n.I18nizableText; 066import org.ametys.runtime.model.ElementDefinition; 067import org.ametys.runtime.model.ModelHelper; 068import org.ametys.runtime.model.ModelItem; 069import org.ametys.runtime.model.View; 070import org.ametys.runtime.model.type.ModelItemType; 071import org.ametys.runtime.model.type.ModelItemTypeConstants; 072import org.ametys.runtime.parameter.DefaultValidator; 073import org.ametys.runtime.parameter.Errors; 074import org.ametys.runtime.parameter.Validator; 075import org.ametys.runtime.plugin.PluginsManager; 076import org.ametys.runtime.plugin.component.AbstractLogEnabled; 077import org.ametys.runtime.plugin.component.PluginAware; 078 079/** 080 * Use a jdbc driver for getting the list of users, modifying them and also 081 * authenticate them.<br> 082 * Passwords need to be encrypted with MD5 and encoded in base64.<br> 083 */ 084public class JdbcUserDirectory extends AbstractLogEnabled implements ModifiableUserDirectory, Component, Serviceable, Contextualizable, PluginAware, Cacheable, Disposable 085{ 086 /** The base plugin (for i18n key) */ 087 protected static final String BASE_PLUGIN_NAME = "core"; 088 089 static final String[] __COLUMNS = new String[] {"login", "password", "firstname", "lastname", "email"}; 090 static final String[] __ORDERBY_COLUMNS = new String[] {"lastname", "firstname"}; 091 092 /** Name of the parameter holding the datasource id */ 093 private static final String __DATASOURCE_PARAM_NAME = "runtime.users.jdbc.datasource"; 094 /** Name of the parameter holding the table users' name */ 095 private static final String __USERS_TABLE_PARAM_NAME = "runtime.users.jdbc.table"; 096 097 private static final String __COLUMN_LOGIN = "login"; 098 private static final String __COLUMN_PASSWORD = "password"; 099 private static final String __COLUMN_FIRSTNAME = "firstname"; 100 private static final String __COLUMN_LASTNAME = "lastname"; 101 private static final String __COLUMN_EMAIL = "email"; 102 private static final String __COLUMN_SALT = "salt"; 103 104 private static final String __JDBC_USERDIRECTORY_USER_BY_LOGIN_CACHE_NAME_PREFIX = JdbcUserDirectory.class.getName() + "$by.login$"; 105 private static final String __JDBC_USERDIRECTORY_USER_BY_MAIL_CACHE_NAME_PREFIX = JdbcUserDirectory.class.getName() + "$by.mail$"; 106 107 /** The identifier of data source */ 108 protected String _dataSourceId; 109 /** The name of users' SQL table */ 110 protected String _userTableName; 111 112 /** Model */ 113 protected Map<String, ElementDefinition> _model; 114 115 /** Plugin name */ 116 protected String _pluginName; 117 118 /** The avalon service manager */ 119 protected ServiceManager _manager; 120 121 /** The avalon context */ 122 protected Context _context; 123 124 /** The cocoon source resolver */ 125 protected SourceResolver _sourceResolver; 126 127 private ObservationManager _observationManager; 128 private CurrentUserProvider _currentUserProvider; 129 130 private String _udModelId; 131 private Map<String, Object> _paramValues; 132 private String _populationId; 133 134 private String _label; 135 136 private String _id; 137 138 // Cannot use _populationId + "#" + _id as two UserDirectories with same id can co-exist during a short amount of time (during UserPopulationDAO#_readPopulations) 139 private final String _uniqueCacheSuffix = org.ametys.core.util.StringUtils.generateKey(); 140 141 private boolean _lazyInitialized; 142 143 private JdbcParameterTypeExtensionPoint _jdbcParameterTypeExtensionPoint; 144 private AbstractCacheManager _cacheManager; 145 146 private View _view; 147 148 private MigrationExtensionPoint _migrationEP; 149 150 private SqlVersionStorage _sqlVersionStorage; 151 152 @Override 153 public void setPluginInfo(String pluginName, String featureName, String id) 154 { 155 _pluginName = pluginName; 156 } 157 158 @Override 159 public void contextualize(Context context) throws ContextException 160 { 161 _context = context; 162 } 163 164 @Override 165 public void service(ServiceManager manager) throws ServiceException 166 { 167 _manager = manager; 168 _sourceResolver = (SourceResolver) manager.lookup(SourceResolver.ROLE); 169 _jdbcParameterTypeExtensionPoint = (JdbcParameterTypeExtensionPoint) manager.lookup(JdbcParameterTypeExtensionPoint.ROLE); 170 _cacheManager = (AbstractCacheManager) manager.lookup(AbstractCacheManager.ROLE); 171 _migrationEP = (MigrationExtensionPoint) manager.lookup(MigrationExtensionPoint.ROLE); 172 VersionStorageExtensionPoint versionStorageEP = (VersionStorageExtensionPoint) manager.lookup(VersionStorageExtensionPoint.ROLE); 173 _sqlVersionStorage = (SqlVersionStorage) versionStorageEP.getExtension("sql"); 174 } 175 176 public String getId() 177 { 178 return _id; 179 } 180 181 public String getFamilyId() 182 { 183 return JdbcUserDirectory.class.getName(); 184 } 185 186 public String getLabel() 187 { 188 return _label; 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_USERDIRECTORY_USER_BY_LOGIN_CACHE_NAME_PREFIX + _uniqueCacheSuffix, 203 _buildI18n("PLUGINS_CORE_USERS_JDBC_CACHE_BY_LOGIN_LABEL"), 204 _buildI18n("PLUGINS_CORE_USERS_JDBC_CACHE_BY_LOGIN_DESC")), 205 SingleCacheConfiguration.of( 206 __JDBC_USERDIRECTORY_USER_BY_MAIL_CACHE_NAME_PREFIX + _uniqueCacheSuffix, 207 _buildI18n("PLUGINS_CORE_USERS_JDBC_CACHE_BY_MAIL_LABEL"), 208 _buildI18n("PLUGINS_CORE_USERS_JDBC_CACHE_BY_MAIL_DESC")) 209 ); 210 } 211 212 private I18nizableText _buildI18n(String i18Key) 213 { 214 String catalogue = "plugin.core-impl"; 215 I18nizableText userDirectoryId = new I18nizableText(getPopulationId() + "#" + getId()); 216 Map<String, I18nizableTextParameter> params = Map.of("id", userDirectoryId); 217 return new I18nizableText(catalogue, i18Key, params); 218 } 219 220 private Cache<String, User> _getCacheByLogin() 221 { 222 return getCache(__JDBC_USERDIRECTORY_USER_BY_LOGIN_CACHE_NAME_PREFIX + _uniqueCacheSuffix); 223 } 224 225 private Cache<String, User> _getCacheByMail() 226 { 227 return getCache(__JDBC_USERDIRECTORY_USER_BY_MAIL_CACHE_NAME_PREFIX + _uniqueCacheSuffix); 228 } 229 230 @Override 231 public AbstractCacheManager getCacheManager() 232 { 233 return _cacheManager; 234 } 235 236 @Override 237 public void init(String id, String udModelId, Map<String, Object> paramValues, String label) 238 { 239 _id = id; 240 _udModelId = udModelId; 241 _paramValues = paramValues; 242 _label = label; 243 244 _userTableName = (String) paramValues.get(__USERS_TABLE_PARAM_NAME); 245 _dataSourceId = (String) paramValues.get(__DATASOURCE_PARAM_NAME); 246 247 _initModelParameters(); 248 249 createCaches(); 250 } 251 252 private void _initModelParameters() 253 { 254 _model = new LinkedHashMap<>(); 255 256 I18nizableText invalidLoginText = new I18nizableText("plugin." + BASE_PLUGIN_NAME, "PLUGINS_CORE_USERS_JDBC_FIELD_LOGIN_INVALID"); 257 Validator loginValidator = new DefaultValidator("^[a-zA-Z0-9_\\-\\.@]{3,64}$", invalidLoginText, true); 258 _initModelParameter(__COLUMN_LOGIN, ModelItemTypeConstants.STRING_TYPE_ID, "PLUGINS_CORE_USERS_JDBC_FIELD_LOGIN_LABEL", "PLUGINS_CORE_USERS_JDBC_FIELD_LOGIN_DESCRIPTION", loginValidator); 259 260 _initModelParameter(__COLUMN_PASSWORD, ModelItemTypeConstants.PASSWORD_ELEMENT_TYPE_ID, "PLUGINS_CORE_USERS_JDBC_FIELD_PASSWORD_LABEL", "PLUGINS_CORE_USERS_JDBC_FIELD_PASSWORD_DESCRIPTION", null); 261 262 _initModelParameter(__COLUMN_FIRSTNAME, ModelItemTypeConstants.STRING_TYPE_ID, "PLUGINS_CORE_USERS_JDBC_FIELD_FIRSTNAME_LABEL", "PLUGINS_CORE_USERS_JDBC_FIELD_FIRSTNAME_DESCRIPTION", null); 263 264 _initModelParameter(__COLUMN_LASTNAME, ModelItemTypeConstants.STRING_TYPE_ID, "PLUGINS_CORE_USERS_JDBC_FIELD_LASTNAME_LABEL", "PLUGINS_CORE_USERS_JDBC_FIELD_LASTNAME_DESCRIPTION", null); 265 266 I18nizableText invalidEmailText = new I18nizableText("plugin." + BASE_PLUGIN_NAME, "PLUGINS_CORE_USERS_JDBC_FIELD_EMAIL_INVALID"); 267 Validator emailValidator = new DefaultValidator(SendMailHelper.EMAIL_VALIDATION_REGEXP, invalidEmailText, false); 268 _initModelParameter(__COLUMN_EMAIL, ModelItemTypeConstants.STRING_TYPE_ID, "PLUGINS_CORE_USERS_JDBC_FIELD_EMAIL_LABEL", "PLUGINS_CORE_USERS_JDBC_FIELD_EMAIL_DESCRIPTION", emailValidator); 269 } 270 271 private void _initModelParameter(String name, String parameterType, String labelKey, String descriptionKey, Validator validator) 272 { 273 ModelItemType modelItemType = _jdbcParameterTypeExtensionPoint.getExtension(parameterType); 274 275 ElementDefinition parameter = new ElementDefinition<>(); 276 parameter.setPluginName(BASE_PLUGIN_NAME); 277 parameter.setType(modelItemType); 278 parameter.setName(name); 279 parameter.setLabel(new I18nizableText("plugin." + BASE_PLUGIN_NAME, labelKey)); 280 parameter.setDescription(new I18nizableText("plugin." + BASE_PLUGIN_NAME, descriptionKey)); 281 parameter.setValidator(validator != null ? validator : new DefaultValidator(null, true)); 282 283 _model.put(name, parameter); 284 } 285 286 /** 287 * Lazy lookup the {@link ObservationManager} 288 * @return the observation manager 289 */ 290 protected ObservationManager getObservationManager() 291 { 292 if (_observationManager == null) 293 { 294 try 295 { 296 _observationManager = (ObservationManager) _manager.lookup(ObservationManager.ROLE); 297 } 298 catch (ServiceException e) 299 { 300 // We may be in safe mode 301 if (PluginsManager.getInstance().isSafeMode()) 302 { 303 getLogger().debug("Unable to lookup ObservationManager component in safe mode", e); 304 } 305 else 306 { 307 getLogger().error("Unable to lookup ObservationManager component", e); 308 } 309 } 310 } 311 return _observationManager; 312 } 313 314 /** 315 * Lazy lookup the {@link CurrentUserProvider} 316 * @return the current user provider 317 */ 318 protected CurrentUserProvider getCurrentUserProvider() 319 { 320 if (_currentUserProvider == null) 321 { 322 try 323 { 324 _currentUserProvider = (CurrentUserProvider) _manager.lookup(CurrentUserProvider.ROLE); 325 } 326 catch (ServiceException e) 327 { 328 throw new RuntimeException("Unable to lookup CurrentUserProvider component", e); 329 } 330 } 331 return _currentUserProvider; 332 } 333 334 /** 335 * Get the connection to the database 336 * @return the SQL connection 337 */ 338 @SuppressWarnings("unchecked") 339 protected Connection getSQLConnection() 340 { 341 Connection connection = ConnectionHelper.getConnection(_dataSourceId); 342 343 if (!_lazyInitialized) 344 { 345 try 346 { 347 String componentId = "user-directory.jdbc"; 348 String versionId = "org.ametys.plugins.core.user.directory.Jdbc.upgrade_" + _userTableName; 349 SQLScriptHelper.createTableIfNotExists(_dataSourceId, _userTableName, "plugin:core://scripts/%s/jdbc_users.template.sql", _sourceResolver, 350 (Map) ArrayUtils.toMap(new String[][] {{"%TABLENAME%", _userTableName}}), componentId, versionId, _migrationEP, _sqlVersionStorage); 351 } 352 catch (Exception e) 353 { 354 getLogger().error("The tables requires by the " + this.getClass().getName() + " could not be created. A degraded behavior will occur", e); 355 } 356 357 _lazyInitialized = true; 358 } 359 360 return connection; 361 } 362 363 @Override 364 public void setPopulationId(String populationId) 365 { 366 _populationId = populationId; 367 } 368 369 @Override 370 public String getPopulationId() 371 { 372 return _populationId; 373 } 374 375 @Override 376 public Map<String, Object> getParameterValues() 377 { 378 return _paramValues; 379 } 380 381 @Override 382 public String getUserDirectoryModelId() 383 { 384 return _udModelId; 385 } 386 387 @SuppressWarnings("unchecked") 388 @Override 389 public Collection<User> getUsers() 390 { 391 return getUsers(Integer.MAX_VALUE, 0, Collections.EMPTY_MAP); 392 } 393 394 @Override 395 public List<User> getUsers(int count, int offset, Map<String, Object> parameters) 396 { 397 String pattern = StringUtils.defaultIfEmpty((String) parameters.get("pattern"), null); 398 int boundedCount = count >= 0 ? count : Integer.MAX_VALUE; 399 int boundedOffset = offset >= 0 ? offset : 0; 400 401 SelectUsersJdbcQueryExecutor<List<User>> queryExecutor = new SelectUsersJdbcQueryExecutor<>(pattern, boundedCount, boundedOffset) 402 { 403 @Override 404 protected List<User> processResultSet(ResultSet rs) throws SQLException 405 { 406 Cache<String, User> cache = isCachingEnabled() ? _getCacheByLogin() : null; 407 return _getUsersProcessResultSet(rs, cache); 408 } 409 }; 410 411 return queryExecutor.run(); 412 } 413 414 @Override 415 public User getUser(String login) 416 { 417 if (isCachingEnabled() && _getCacheByLogin().hasKey(login)) 418 { 419 User user = _getCacheByLogin().get(login); 420 return user; 421 } 422 423 SelectUserJdbcQueryExecutor<User> queryExecutor = new SelectUserJdbcQueryExecutor<>(login) 424 { 425 @Override 426 protected User processResultSet(ResultSet rs) throws SQLException 427 { 428 Cache<String, User> cache = isCachingEnabled() ? _getCacheByLogin() : null; 429 return _getUserProcessResultSet(rs, login, cache); 430 } 431 }; 432 433 return queryExecutor.run(); 434 } 435 436 @Override 437 public User getUserByEmail(String email) throws NotUniqueUserException 438 { 439 if (isCachingEnabled() && _getCacheByMail().hasKey(email)) 440 { 441 User user = _getCacheByMail().get(email); 442 return user; 443 } 444 445 SelectUserJdbcQueryExecutor<List<User>> queryExecutor = new SelectUserJdbcQueryExecutor<>(email, __COLUMN_EMAIL) 446 { 447 @Override 448 protected List<User> processResultSet(ResultSet rs) throws SQLException 449 { 450 Cache<String, User> mailCache = isCachingEnabled() ? _getCacheByMail() : null; 451 return _getUsersProcessResultSet(rs, mailCache); 452 } 453 }; 454 455 List<User> users = queryExecutor.run(); 456 if (users.size() == 1) 457 { 458 return users.get(0); 459 } 460 else if (users.isEmpty()) 461 { 462 return null; 463 } 464 else 465 { 466 throw new NotUniqueUserException("Find " + users.size() + " users matching the email " + email); 467 } 468 469 } 470 471 @Override 472 public boolean checkCredentials(String login, String password) 473 { 474 boolean updateNeeded = false; 475 476 Connection con = null; 477 PreparedStatement stmt = null; 478 ResultSet rs = null; 479 try 480 { 481 // Connect to the database with connection pool 482 con = getSQLConnection(); 483 484 // Build request for authenticating the user 485 String sql = "SELECT " + __COLUMN_LOGIN + ", " + __COLUMN_PASSWORD + ", " + __COLUMN_SALT + " FROM " + _userTableName + " WHERE " + __COLUMN_LOGIN + " = ?"; 486 if (getLogger().isDebugEnabled()) 487 { 488 getLogger().debug(sql); 489 } 490 491 stmt = con.prepareStatement(sql); 492 stmt.setString(1, login); 493 494 // Do the request 495 rs = stmt.executeQuery(); 496 497 if (rs.next()) 498 { 499 String storedPassword = rs.getString(__COLUMN_PASSWORD); 500 String salt = rs.getString(__COLUMN_SALT); 501 502 if (salt == null && _isMD5Encrypted(storedPassword)) 503 { 504 String encryptedPassword = org.ametys.core.util.StringUtils.md5Base64(password); 505 506 if (encryptedPassword == null) 507 { 508 getLogger().error("Unable to encrypt password"); 509 return false; 510 } 511 512 if (storedPassword.equals(encryptedPassword)) 513 { 514 updateNeeded = true; 515 return true; 516 } 517 518 return false; 519 } 520 else 521 { 522 String encryptedPassword = DigestUtils.sha512Hex(salt + password); 523 524 if (encryptedPassword == null) 525 { 526 getLogger().error("Unable to encrypt password"); 527 return false; 528 } 529 530 return storedPassword.equalsIgnoreCase(encryptedPassword); 531 } 532 } 533 534 return false; 535 } 536 catch (SQLException e) 537 { 538 getLogger().error("Error during the connection to the database", e); 539 return false; 540 } 541 finally 542 { 543 // Close connections 544 ConnectionHelper.cleanup(rs); 545 ConnectionHelper.cleanup(stmt); 546 ConnectionHelper.cleanup(con); 547 548 if (updateNeeded) 549 { 550 _updateToSSHAPassword(login, password); 551 } 552 } 553 } 554 555 @Override 556 public void add(Map<String, String> userInformation) throws InvalidModificationException 557 { 558 Connection con = null; 559 PreparedStatement stmt = null; 560 561 if (getLogger().isDebugEnabled()) 562 { 563 getLogger().debug("Starting adding a new user"); 564 } 565 566 // Check the presence of all parameters 567 Map<String, Errors> errorFields = validate(userInformation); 568 569 if (errorFields.size() > 0) 570 { 571 throw new InvalidModificationException("The creation of user failed because of invalid parameter values", errorFields); 572 } 573 574 String login = userInformation.get("login"); 575 576 try 577 { 578 // Connect to the database with connection pool 579 con = getSQLConnection(); 580 581 stmt = createAddStatement(con, userInformation); 582 583 // Do the request and check the result 584 if (stmt.executeUpdate() != 1) 585 { 586 if (getLogger().isWarnEnabled()) 587 { 588 getLogger().warn("The user to remove '" + login + "' was not removed."); 589 } 590 throw new InvalidModificationException("Error no user inserted"); 591 } 592 593 if (getObservationManager() != null) 594 { 595 // Observation manager can be null in safe mode 596 Map<String, Object> eventParams = new HashMap<>(); 597 eventParams.put(ObservationConstants.ARGS_USER, new UserIdentity(login, _populationId)); 598 getObservationManager().notify(new Event(ObservationConstants.EVENT_USER_ADDED, getCurrentUserProvider().getUser(), eventParams)); 599 } 600 } 601 catch (SQLException e) 602 { 603 getLogger().error("Error communication with database", e); 604 throw new InvalidModificationException("Error during the communication with the database", e); 605 } 606 finally 607 { 608 // Close connections 609 ConnectionHelper.cleanup(stmt); 610 ConnectionHelper.cleanup(con); 611 } 612 613 } 614 615 @Override 616 public Map<String, Errors> validate(Map<String, String> userInformation) 617 { 618 Map<String, Errors> errorFields = new HashMap<>(); 619 for (ElementDefinition parameter : _model.values()) 620 { 621 Object typedValue = parameter.getType().castValue(userInformation.get(parameter.getName())); 622 Errors errors = new Errors(); 623 624 List<I18nizableText> errorsList = ModelHelper.validateValue(parameter, typedValue); 625 for (I18nizableText error : errorsList) 626 { 627 errors.addError(error); 628 } 629 630 if (errors.hasErrors()) 631 { 632 if (getLogger().isDebugEnabled()) 633 { 634 getLogger().debug("The field '" + parameter.getName() + "' is not valid"); 635 } 636 errorFields.put(parameter.getName(), errors); 637 } 638 } 639 return errorFields; 640 } 641 642 @Override 643 public void update(Map<String, String> userInformation) throws InvalidModificationException 644 { 645 Connection con = null; 646 PreparedStatement stmt = null; 647 648 Map<String, Errors> errorFields = new HashMap<>(); 649 for (String id : userInformation.keySet()) 650 { 651 ElementDefinition parameter = _model.get(id); 652 if (parameter != null) 653 { 654 Object typedValue = parameter.getType().castValue(userInformation.get(parameter.getName())); 655 List<I18nizableText> errorsList = ModelHelper.validateValue(parameter, typedValue); 656 Errors errors = new Errors(); 657 for (I18nizableText error : errorsList) 658 { 659 errors.addError(error); 660 } 661 662 if (errors.hasErrors()) 663 { 664 if (getLogger().isDebugEnabled()) 665 { 666 getLogger().debug("The field '" + parameter.getName() + "' is not valid"); 667 } 668 errorFields.put(parameter.getName(), errors); 669 } 670 } 671 } 672 673 if (errorFields.size() > 0) 674 { 675 throw new InvalidModificationException("The modification of user failed because of invalid parameter values", errorFields); 676 } 677 678 String login = userInformation.get("login"); 679 if (StringUtils.isEmpty(login)) 680 { 681 throw new InvalidModificationException("Cannot update without login information"); 682 } 683 684 try 685 { 686 // Connect to the database with connection pool 687 con = getSQLConnection(); 688 689 stmt = createModifyStatement(con, userInformation); 690 691 // Do the request 692 if (stmt.executeUpdate() != 1) 693 { 694 throw new InvalidModificationException("Error. User '" + login + "' not updated"); 695 } 696 697 if (getObservationManager() != null) 698 { 699 // Observation manager can be null in safe mode 700 Map<String, Object> eventParams = new HashMap<>(); 701 eventParams.put(ObservationConstants.ARGS_USER, new UserIdentity(login, _populationId)); 702 getObservationManager().notify(new Event(ObservationConstants.EVENT_USER_UPDATED, getCurrentUserProvider().getUser(), eventParams)); 703 } 704 705 if (isCachingEnabled()) 706 { 707 _getCacheByLogin().invalidate(login); 708 } 709 } 710 catch (SQLException e) 711 { 712 getLogger().error("Error communication with database", e); 713 throw new InvalidModificationException("Error communication with database", e); 714 } 715 finally 716 { 717 // Close connections 718 ConnectionHelper.cleanup(stmt); 719 ConnectionHelper.cleanup(con); 720 } 721 } 722 723 @Override 724 public void remove(String login) throws InvalidModificationException 725 { 726 Connection con = null; 727 PreparedStatement stmt = null; 728 729 try 730 { 731 // Connect to the database with connection pool 732 con = getSQLConnection(); 733 734 // Build request for removing the user 735 String sqlRequest = "DELETE FROM " + _userTableName + " WHERE " + __COLUMN_LOGIN + " = ?"; 736 if (getLogger().isDebugEnabled()) 737 { 738 getLogger().debug(sqlRequest); 739 } 740 741 stmt = con.prepareStatement(sqlRequest); 742 stmt.setString(1, login); 743 744 // Do the request and check the result 745 if (stmt.executeUpdate() != 1) 746 { 747 throw new InvalidModificationException("Error user was not deleted"); 748 } 749 750 if (getObservationManager() != null) 751 { 752 // Observation manager can be null in safe mode 753 Map<String, Object> eventParams = new HashMap<>(); 754 eventParams.put(ObservationConstants.ARGS_USER, new UserIdentity(login, _populationId)); 755 getObservationManager().notify(new Event(ObservationConstants.EVENT_USER_DELETED, getCurrentUserProvider().getUser(), eventParams)); 756 } 757 758 if (isCachingEnabled()) 759 { 760 _getCacheByLogin().invalidate(login); 761 } 762 } 763 catch (SQLException e) 764 { 765 throw new InvalidModificationException("Error during the communication with the database", e); 766 } 767 finally 768 { 769 // Close connections 770 ConnectionHelper.cleanup(stmt); 771 ConnectionHelper.cleanup(con); 772 } 773 } 774 775 public Collection< ? extends ModelItem> getModelItems() 776 { 777 return Collections.unmodifiableCollection(_model.values()); 778 } 779 780 /** 781 * Get the mandatory predicate to use when querying users by pattern. 782 * @param pattern The pattern to match, can be null. 783 * @return a {@link JdbcPredicate}, can be null. 784 */ 785 protected JdbcPredicate _getMandatoryPredicate(String pattern) 786 { 787 return null; 788 } 789 790 /** 791 * Get the pattern to match user login 792 * @param pattern the pattern 793 * @return the pattern to match user login 794 */ 795 protected String _getPatternToMatch(String pattern) 796 { 797 if (pattern != null) 798 { 799 return "%" + pattern + "%"; 800 } 801 return null; 802 } 803 804 /** 805 * Determines if the password is encrypted with MD5 algorithm 806 * @param password The encrypted password 807 * @return true if the password is encrypted with MD5 algorithm 808 */ 809 protected boolean _isMD5Encrypted(String password) 810 { 811 return password.length() == 24; 812 } 813 814 /** 815 * Generate a salt key and encrypt the password with the sha2 algorithm 816 * @param login The user login 817 * @param password The user pasword 818 */ 819 protected void _updateToSSHAPassword(String login, String password) 820 { 821 Connection con = null; 822 PreparedStatement stmt = null; 823 ResultSet rs = null; 824 825 try 826 { 827 con = getSQLConnection(); 828 829 String generateSaltKey = RandomStringUtils.randomAlphanumeric(48); 830 String newEncryptedPassword = DigestUtils.sha512Hex(generateSaltKey + password); 831 832 String sqlUpdate = "UPDATE " + _userTableName + " SET " + __COLUMN_PASSWORD + " = ?, " + __COLUMN_SALT + " = ? WHERE " + __COLUMN_LOGIN + " = ?"; 833 if (getLogger().isDebugEnabled()) 834 { 835 getLogger().debug(sqlUpdate); 836 } 837 838 stmt = con.prepareStatement(sqlUpdate); 839 stmt.setString(1, newEncryptedPassword); 840 stmt.setString(2, generateSaltKey); 841 stmt.setString(3, login); 842 843 stmt.execute(); 844 } 845 catch (SQLException e) 846 { 847 getLogger().error("Error during the connection to the database", e); 848 } 849 finally 850 { 851 // Close connections 852 ConnectionHelper.cleanup(rs); 853 ConnectionHelper.cleanup(stmt); 854 ConnectionHelper.cleanup(con); 855 } 856 } 857 858 /** 859 * Create Add statement 860 * @param con The sql connection 861 * @param userInformation the user informations 862 * @return The statement 863 * @throws SQLException if an error occurred 864 */ 865 protected PreparedStatement createAddStatement(Connection con, Map<String, String> userInformation) throws SQLException 866 { 867 String beginClause = "INSERT INTO " + _userTableName + " ("; 868 String middleClause = ") VALUES ("; 869 String endClause = ")"; 870 871 StringBuffer intoClause = new StringBuffer(); 872 StringBuffer valueClause = new StringBuffer(); 873 874 intoClause.append(__COLUMN_SALT); 875 valueClause.append("?"); 876 877 for (String column : __COLUMNS) 878 { 879 intoClause.append(", " + column); 880 valueClause.append(", ?"); 881 } 882 883 String sqlRequest = beginClause + intoClause.toString() + middleClause + valueClause + endClause; 884 if (getLogger().isDebugEnabled()) 885 { 886 getLogger().debug(sqlRequest); 887 } 888 889 PreparedStatement stmt = con.prepareStatement(sqlRequest); 890 891 int i = 1; 892 boolean clearText = !userInformation.containsKey("clearText") || !"false".equals(userInformation.get("clearText")) || !userInformation.containsKey(__COLUMN_SALT); 893 String generatedSaltKey = clearText ? RandomStringUtils.randomAlphanumeric(48) : userInformation.get(__COLUMN_SALT); 894 895 stmt.setString(i++, generatedSaltKey); 896 897 for (String column : __COLUMNS) 898 { 899 if ("password".equals(column)) 900 { 901 String encryptedPassword; 902 if (clearText) 903 { 904 encryptedPassword = DigestUtils.sha512Hex(generatedSaltKey + userInformation.get(column)); 905 if (encryptedPassword == null) 906 { 907 String message = "Cannot encode password"; 908 getLogger().error(message); 909 throw new SQLException(message); 910 } 911 } 912 else 913 { 914 encryptedPassword = userInformation.get(column); 915 } 916 stmt.setString(i++, encryptedPassword); 917 } 918 else 919 { 920 stmt.setString(i++, userInformation.get(column)); 921 } 922 } 923 924 return stmt; 925 } 926 927 /** 928 * Create statement to update database 929 * @param con The sql connection 930 * @param userInformation The user information 931 * @return The statement 932 * @throws SQLException if an error occurred 933 */ 934 protected PreparedStatement createModifyStatement(Connection con, Map<String, String> userInformation) throws SQLException 935 { 936 // Build request for editing the user 937 String beginClause = "UPDATE " + _userTableName + " SET "; 938 String endClause = " WHERE " + __COLUMN_LOGIN + " = ?"; 939 940 StringBuffer columnNames = new StringBuffer(""); 941 942 boolean passwordUpdate = false; 943 for (String id : userInformation.keySet()) 944 { 945 if (ArrayUtils.contains(__COLUMNS, id) && !"login".equals(id) && !("password".equals(id) && (userInformation.get(id) == null))) 946 { 947 if ("password".equals(id)) 948 { 949 passwordUpdate = true; 950 } 951 952 if (columnNames.length() > 0) 953 { 954 columnNames.append(", "); 955 } 956 columnNames.append(id + " = ?"); 957 } 958 } 959 960 if (passwordUpdate) 961 { 962 columnNames.append(", " + __COLUMN_SALT + " = ?"); 963 } 964 965 String sqlRequest = beginClause + columnNames.toString() + endClause; 966 if (getLogger().isDebugEnabled()) 967 { 968 getLogger().debug(sqlRequest); 969 } 970 971 PreparedStatement stmt = con.prepareStatement(sqlRequest); 972 _fillModifyStatement(stmt, userInformation); 973 974 return stmt; 975 } 976 977 /** 978 * Fill the statement with the user informations 979 * @param stmt The statement of the sql request 980 * @param userInformation the user informations 981 * @throws SQLException if an error occurred 982 */ 983 protected void _fillModifyStatement(PreparedStatement stmt, Map<String, String> userInformation) throws SQLException 984 { 985 int index = 1; 986 987 boolean clearText = !userInformation.containsKey("clearText") || !"false".equals(userInformation.get("clearText")) || !userInformation.containsKey(__COLUMN_SALT); 988 String generatedSaltKey = clearText ? RandomStringUtils.randomAlphanumeric(48) : userInformation.get(__COLUMN_SALT); 989 990 boolean passwordUpdate = false; 991 992 for (String id : userInformation.keySet()) 993 { 994 if (ArrayUtils.contains(__COLUMNS, id) && !"login".equals(id)) 995 { 996 if ("password".equals(id)) 997 { 998 if (userInformation.get(id) != null) 999 { 1000 String encryptedPassword; 1001 if (clearText) 1002 { 1003 encryptedPassword = DigestUtils.sha512Hex(generatedSaltKey + userInformation.get(id)); 1004 if (encryptedPassword == null) 1005 { 1006 String message = "Cannot encrypt password"; 1007 getLogger().error(message); 1008 throw new SQLException(message); 1009 } 1010 } 1011 else 1012 { 1013 encryptedPassword = userInformation.get(id); 1014 } 1015 stmt.setString(index++, encryptedPassword); 1016 passwordUpdate = true; 1017 } 1018 } 1019 else 1020 { 1021 stmt.setString(index++, userInformation.get(id)); 1022 } 1023 } 1024 } 1025 1026 if (passwordUpdate) 1027 { 1028 stmt.setString(index++, generatedSaltKey); 1029 } 1030 1031 stmt.setString(index++, userInformation.get("login")); 1032 } 1033 1034 /** 1035 * Populate the user list with the result set 1036 * @param rs The result set 1037 * @param cache the cache to use. Is null if caching is not enabled 1038 * @return The user list 1039 * @throws SQLException If an SQL exception occurs 1040 */ 1041 protected List<User> _getUsersProcessResultSet(ResultSet rs, Cache<String, User> cache) throws SQLException 1042 { 1043 List<User> users = new ArrayList<>(); 1044 1045 while (rs.next()) 1046 { 1047 User user = null; 1048 1049 // Try to get in cache 1050 if (isCachingEnabled()) 1051 { 1052 String login = rs.getString(__COLUMN_LOGIN); 1053 user = cache.hasKey(login) ? cache.get(login) : null; 1054 } 1055 1056 // Or create from result set 1057 if (user == null) 1058 { 1059 user = _createUserFromResultSet(rs); 1060 1061 if (isCachingEnabled()) 1062 { 1063 cache.put(user.getIdentity().getLogin(), user); 1064 } 1065 } 1066 1067 users.add(user); 1068 } 1069 1070 return users; 1071 } 1072 1073 /** 1074 * Create the user implementation from the result set of the request 1075 * 1076 * @param rs The result set where you can use get methods 1077 * @return The user reflecting the current cursor position in the result set 1078 * @throws SQLException if an error occurred 1079 */ 1080 protected User _createUserFromResultSet(ResultSet rs) throws SQLException 1081 { 1082 String login = rs.getString(__COLUMN_LOGIN); 1083 String lastName = rs.getString(__COLUMN_LASTNAME); 1084 String firstName = rs.getString(__COLUMN_FIRSTNAME); 1085 String email = rs.getString(__COLUMN_EMAIL); 1086 1087 return new User(new UserIdentity(login, _populationId), lastName, firstName, email, this); 1088 } 1089 1090 /** 1091 * Retrieve an user from a result set 1092 * @param rs The result set 1093 * @param login The user login 1094 * @param cache the cache to use. Is null if caching is not enabled 1095 * @return The retrieved user or null if not found 1096 * @throws SQLException If an SQL Exception occurs 1097 */ 1098 protected User _getUserProcessResultSet(ResultSet rs, String login, Cache<String, User> cache) throws SQLException 1099 { 1100 if (rs.next()) 1101 { 1102 // Retrieve information of the user 1103 User user = _createUserFromResultSet(rs); 1104 1105 if (isCachingEnabled()) 1106 { 1107 cache.put(login, user); 1108 } 1109 1110 return user; 1111 } 1112 else 1113 { 1114 // no user with this login in the database 1115 return null; 1116 } 1117 } 1118 1119 @Override 1120 public View getView() 1121 { 1122 if (_view == null) 1123 { 1124 _view = View.of(this, __COLUMNS); 1125 } 1126 return _view; 1127 } 1128 1129 // ------------------------------ 1130 // INNER CLASSE 1131 // ------------------------------ 1132 /** 1133 * An internal query executor. 1134 * @param <T> The type of the queried object 1135 */ 1136 protected abstract class AbstractJdbcQueryExecutor<T> 1137 { 1138 /** 1139 * Main function, run the query process. Will not throw exception. Use 1140 * runWithException to catch non SQL exception thrown by 1141 * {@link #processResultSet(ResultSet)} 1142 * @return The queried object or null 1143 */ 1144 @SuppressWarnings("synthetic-access") 1145 public T run() 1146 { 1147 try 1148 { 1149 return runWithException(); 1150 } 1151 catch (Exception e) 1152 { 1153 getLogger().error("Exception during a query execution", e); 1154 throw new RuntimeException("Exception during a query execution", e); 1155 } 1156 } 1157 1158 /** 1159 * Main function, run the query process. 1160 * @return The queried object or null 1161 * @throws Exception All non SQLException will be thrown 1162 */ 1163 @SuppressWarnings("synthetic-access") 1164 public T runWithException() throws Exception 1165 { 1166 Connection connection = null; 1167 PreparedStatement stmt = null; 1168 ResultSet rs = null; 1169 1170 try 1171 { 1172 connection = getSQLConnection(); 1173 1174 String sql = getSqlQuery(connection); 1175 1176 if (getLogger().isDebugEnabled()) 1177 { 1178 getLogger().debug("Executing SQL query: " + sql); 1179 } 1180 1181 stmt = prepareStatement(connection, sql); 1182 rs = executeQuery(stmt); 1183 1184 return processResultSet(rs); 1185 } 1186 catch (SQLException e) 1187 { 1188 getLogger().error("Error during the communication with the database", e); 1189 throw new RuntimeException("Error during the communication with the database", e); 1190 } 1191 finally 1192 { 1193 ConnectionHelper.cleanup(rs); 1194 ConnectionHelper.cleanup(stmt); 1195 ConnectionHelper.cleanup(connection); 1196 } 1197 } 1198 1199 /** 1200 * Must return the SQL query to execute 1201 * @param connection The pool connection 1202 * @return The SQL query 1203 */ 1204 protected abstract String getSqlQuery(Connection connection); 1205 1206 /** 1207 * Prepare the statement to execute 1208 * @param connection The pool connection 1209 * @param sql The SQL query 1210 * @return The prepared statement, ready to be executed 1211 * @throws SQLException If an SQL Exception occurs 1212 */ 1213 protected PreparedStatement prepareStatement(Connection connection, String sql) throws SQLException 1214 { 1215 return connection.prepareStatement(sql); 1216 } 1217 1218 /** 1219 * Execute the prepared statement and retrieves the result set. 1220 * @param stmt The prepared statement 1221 * @return The result set 1222 * @throws SQLException If an SQL Exception occurs 1223 */ 1224 protected ResultSet executeQuery(PreparedStatement stmt) throws SQLException 1225 { 1226 return stmt.executeQuery(); 1227 } 1228 1229 /** 1230 * Process the result set 1231 * @param rs The result set 1232 * @return The queried object or null 1233 * @throws SQLException If an SQL exception occurs 1234 * @throws Exception Other exception will be thrown when using {@link #runWithException()} 1235 */ 1236 protected T processResultSet(ResultSet rs) throws SQLException, Exception 1237 { 1238 return null; 1239 } 1240 } 1241 1242 /** 1243 * Query executor in order to select an user 1244 * @param <T> The type of the queried object 1245 */ 1246 protected class SelectUserJdbcQueryExecutor<T> extends AbstractJdbcQueryExecutor<T> 1247 { 1248 /** The user login */ 1249 protected String _value; 1250 /** The search column */ 1251 protected String _searchColumn; 1252 1253 /** 1254 * The constructor 1255 * @param value The strict value to search for 1256 */ 1257 protected SelectUserJdbcQueryExecutor(String value) 1258 { 1259 _value = value; 1260 _searchColumn = __COLUMN_LOGIN; 1261 } 1262 1263 /** 1264 * The constructor 1265 * @param value The strict value to search for 1266 * @param searchColumn The name of search column 1267 */ 1268 protected SelectUserJdbcQueryExecutor(String value, String searchColumn) 1269 { 1270 _value = value; 1271 _searchColumn = searchColumn; 1272 } 1273 1274 @Override 1275 protected String getSqlQuery(Connection connection) 1276 { 1277 // Build SQL request 1278 StringBuilder selectClause = new StringBuilder(); 1279 for (String id : __COLUMNS) 1280 { 1281 if (selectClause.length() > 0) 1282 { 1283 selectClause.append(", "); 1284 } 1285 selectClause.append(id); 1286 } 1287 1288 StringBuilder sql = new StringBuilder("SELECT "); 1289 sql.append(selectClause).append(" FROM ").append(_userTableName); 1290 sql.append(" WHERE ").append(_searchColumn).append(" = ?"); 1291 1292 return sql.toString(); 1293 } 1294 1295 @Override 1296 protected PreparedStatement prepareStatement(Connection connection, String sql) throws SQLException 1297 { 1298 PreparedStatement stmt = super.prepareStatement(connection, sql); 1299 1300 stmt.setString(1, _value); 1301 return stmt; 1302 } 1303 } 1304 1305 /** 1306 * Query executor in order to select users 1307 * @param <T> The type of the queried object 1308 */ 1309 protected class SelectUsersJdbcQueryExecutor<T> extends AbstractJdbcQueryExecutor<T> 1310 { 1311 /** The pattern to match (none if null) */ 1312 protected String _pattern; 1313 /** The maximum number of users to select */ 1314 protected int _length; 1315 /** The offset to start with, first is 0 */ 1316 protected int _offset; 1317 1318 /** The mandatory predicate to use when querying users by pattern */ 1319 protected JdbcPredicate _mandatoryPredicate; 1320 /** The pattern to match, extracted from the pattern */ 1321 protected String _patternToMatch; 1322 1323 /** 1324 * The constructor 1325 * @param pattern The pattern to match (none if null). 1326 * @param length The maximum number of users to select. 1327 * @param offset The offset to start with, first is 0. 1328 */ 1329 protected SelectUsersJdbcQueryExecutor(String pattern, int length, int offset) 1330 { 1331 _pattern = pattern; 1332 _length = length; 1333 _offset = offset; 1334 } 1335 1336 @Override 1337 protected String getSqlQuery(Connection connection) 1338 { 1339 // Build SQL request 1340 StringBuilder selectClause = new StringBuilder(); 1341 for (String column : __COLUMNS) 1342 { 1343 if (selectClause.length() > 0) 1344 { 1345 selectClause.append(", "); 1346 } 1347 selectClause.append(column); 1348 } 1349 1350 StringBuilder sql = new StringBuilder("SELECT "); 1351 sql.append(selectClause).append(" FROM ").append(_userTableName); 1352 1353 // Add the pattern 1354 _mandatoryPredicate = _getMandatoryPredicate(_pattern); 1355 if (_mandatoryPredicate != null) 1356 { 1357 sql.append(" WHERE ").append(_mandatoryPredicate.getPredicate()); 1358 } 1359 1360 _patternToMatch = _getPatternToMatch(_pattern); 1361 if (_patternToMatch != null) 1362 { 1363 if (ConnectionHelper.DATABASE_DERBY.equals(ConnectionHelper.getDatabaseType(connection))) 1364 { 1365 // The LIKE operator in Derby is case sensitive 1366 sql.append(_mandatoryPredicate != null ? " AND (" : " WHERE ") 1367 .append("UPPER(").append(__COLUMN_LOGIN).append(") LIKE UPPER(?) OR ") 1368 .append("UPPER(").append(__COLUMN_LASTNAME).append(") LIKE UPPER(?) OR ") 1369 .append("UPPER(").append(__COLUMN_FIRSTNAME).append(") LIKE UPPER(?)"); 1370 } 1371 else 1372 { 1373 sql.append(_mandatoryPredicate != null ? " AND (" : " WHERE ") 1374 .append(__COLUMN_LOGIN).append(" LIKE ? OR ") 1375 .append(__COLUMN_LASTNAME).append(" LIKE ? OR ") 1376 .append(__COLUMN_FIRSTNAME).append(" LIKE ?"); 1377 } 1378 1379 if (_mandatoryPredicate != null) 1380 { 1381 sql.append(')'); 1382 } 1383 } 1384 1385 StringBuilder orderByClause = new StringBuilder(); 1386 for (String column : __ORDERBY_COLUMNS) 1387 { 1388 orderByClause.append(orderByClause.length() == 0 ? " ORDER BY " : ", "); 1389 orderByClause.append(column); 1390 } 1391 1392 sql.append(orderByClause); 1393 1394 // Add length filters 1395 sql = _addQuerySize(_length, _offset, connection, selectClause, sql); 1396 1397 return sql.toString(); 1398 } 1399 1400 @SuppressWarnings("synthetic-access") 1401 private StringBuilder _addQuerySize(int length, int offset, Connection con, StringBuilder selectClause, StringBuilder sql) 1402 { 1403 // Do not add anything if not necessary 1404 if (length == Integer.MAX_VALUE && offset == 0) 1405 { 1406 return sql; 1407 } 1408 1409 String dbType = ConnectionHelper.getDatabaseType(con); 1410 1411 if (ConnectionHelper.DATABASE_MYSQL.equals(dbType) || ConnectionHelper.DATABASE_POSTGRES.equals(dbType) || ConnectionHelper.DATABASE_HSQLDB.equals(dbType)) 1412 { 1413 sql.append(" LIMIT " + length + " OFFSET " + offset); 1414 return sql; 1415 } 1416 else if (ConnectionHelper.DATABASE_ORACLE.equals(dbType)) 1417 { 1418 return new StringBuilder("select " + selectClause.toString() + " from (select rownum r, " + selectClause.toString() + " from (" + sql.toString() 1419 + ")) where r BETWEEN " + (offset + 1) + " AND " + (offset + length)); 1420 } 1421 else if (ConnectionHelper.DATABASE_DERBY.equals(dbType)) 1422 { 1423 return new StringBuilder("select ").append(selectClause) 1424 .append(" from (select ROW_NUMBER() OVER () AS ROWNUM, ").append(selectClause.toString()) 1425 .append(" from (").append(sql.toString()).append(") AS TR ) AS TRR where ROWNUM BETWEEN ") 1426 .append(offset + 1).append(" AND ").append(offset + length); 1427 } 1428 else if (getLogger().isWarnEnabled()) 1429 { 1430 getLogger().warn("The request will not have the limit and offset set, since its type is unknown"); 1431 } 1432 1433 return sql; 1434 } 1435 1436 @Override 1437 protected PreparedStatement prepareStatement(Connection connection, String sql) throws SQLException 1438 { 1439 PreparedStatement stmt = super.prepareStatement(connection, sql); 1440 1441 int i = 1; 1442 // Value the parameters if there is a pattern 1443 if (_mandatoryPredicate != null) 1444 { 1445 for (String value : _mandatoryPredicate.getValues()) 1446 { 1447 stmt.setString(i++, value); 1448 } 1449 } 1450 1451 if (_patternToMatch != null) 1452 { 1453 // One for the login, one for the lastname. 1454 stmt.setString(i++, _patternToMatch); 1455 stmt.setString(i++, _patternToMatch); 1456 // FIXME 1457 //if (_parameters.containsKey("firstname")) 1458 //{ 1459 stmt.setString(i++, _patternToMatch); 1460 //} 1461 } 1462 1463 return stmt; 1464 } 1465 } 1466 1467 /** 1468 * Class representing a SQL predicate (to use in a WHERE or HAVING clause), 1469 * with optional string parameters. 1470 */ 1471 public class JdbcPredicate 1472 { 1473 1474 /** The predicate string with optional "?" placeholders. */ 1475 protected String _predicate; 1476 /** The predicate parameter values. */ 1477 protected List<String> _predicateParamValues; 1478 1479 /** 1480 * Build a JDBC predicate. 1481 * @param predicate the predicate string. 1482 * @param values the parameter values. 1483 */ 1484 public JdbcPredicate(String predicate, String... values) 1485 { 1486 this(predicate, Arrays.asList(values)); 1487 } 1488 1489 /** 1490 * Build a JDBC predicate. 1491 * @param predicate the predicate string. 1492 * @param values the parameter values. 1493 */ 1494 public JdbcPredicate(String predicate, List<String> values) 1495 { 1496 this._predicate = predicate; 1497 this._predicateParamValues = values; 1498 } 1499 1500 /** 1501 * Get the predicate. 1502 * @return the predicate 1503 */ 1504 public String getPredicate() 1505 { 1506 return _predicate; 1507 } 1508 1509 /** 1510 * Set the predicate. 1511 * @param predicate the predicate to set 1512 */ 1513 public void setPredicate(String predicate) 1514 { 1515 this._predicate = predicate; 1516 } 1517 1518 /** 1519 * Get the parameter values. 1520 * @return the parameter values. 1521 */ 1522 public List<String> getValues() 1523 { 1524 return _predicateParamValues; 1525 } 1526 1527 /** 1528 * Set the parameter values. 1529 * @param values the parameter values to set. 1530 */ 1531 public void setValues(List<String> values) 1532 { 1533 this._predicateParamValues = values; 1534 } 1535 } 1536 1537}