001/* 002 * Copyright 2016 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.core.datasource; 017 018import java.io.IOException; 019import java.util.ArrayList; 020import java.util.HashMap; 021import java.util.List; 022import java.util.Map; 023 024import org.apache.avalon.framework.component.Component; 025import org.apache.avalon.framework.configuration.ConfigurationException; 026import org.apache.avalon.framework.service.ServiceException; 027import org.apache.avalon.framework.service.ServiceManager; 028import org.apache.avalon.framework.service.Serviceable; 029import org.apache.cocoon.ProcessingException; 030import org.apache.commons.lang3.StringUtils; 031import org.xml.sax.SAXException; 032 033import org.ametys.core.datasource.AbstractDataSourceManager.DataSourceDefinition; 034import org.ametys.core.datasource.DataSourceConsumer.TypeOfUse; 035import org.ametys.core.ui.Callable; 036import org.ametys.core.util.I18nUtils; 037import org.ametys.runtime.i18n.I18nizableText; 038import org.ametys.runtime.model.checker.ItemCheckerTestFailureException; 039import org.ametys.runtime.plugin.component.AbstractLogEnabled; 040 041/** 042 * Component gathering manipulation methods for SQL and LDAP data sources 043 */ 044public class DataSourceClientInteraction extends AbstractLogEnabled implements Component, Serviceable 045{ 046 /** The Avalon role */ 047 public static final String ROLE = DataSourceClientInteraction.class.getName(); 048 049 /** The SQL data source manager */ 050 private SQLDataSourceManager _sqlDataSourceManager; 051 052 /** The LDAP data source manager */ 053 private LDAPDataSourceManager _ldapDataSourceManager; 054 055 /** The extension for data source clients */ 056 private DataSourceConsumerExtensionPoint _dataSourceConsumerEP; 057 058 /** Component gathering utility method allowing to handle internationalizable text */ 059 private I18nUtils _i18nUtils; 060 061 /** 062 * Enum for data source types 063 */ 064 public enum DataSourceType 065 { 066 /** SQL */ 067 SQL, 068 /** LDAP */ 069 LDAP 070 } 071 072 @Override 073 public void service(ServiceManager serviceManager) throws ServiceException 074 { 075 _sqlDataSourceManager = (SQLDataSourceManager) serviceManager.lookup(SQLDataSourceManager.ROLE); 076 _ldapDataSourceManager = (LDAPDataSourceManager) serviceManager.lookup(LDAPDataSourceManager.ROLE); 077 _dataSourceConsumerEP = (DataSourceConsumerExtensionPoint) serviceManager.lookup(DataSourceConsumerExtensionPoint.ROLE); 078 _i18nUtils = (I18nUtils) serviceManager.lookup(I18nUtils.ROLE); 079 } 080 081 /** 082 * Get the existing data sources 083 * @param dataSourceType the data source type. Can be empty or null to get all data sources 084 * @param includePrivate true to include private data sources 085 * @param includeInternal true to include internal data sources 086 * @param includeDefault true to include the default data sources 087 * @param allowedTypes The sub-types of datasource allowed. Can be null. For now, this parameter is only used for sql data sources. The types are the type of databases (mysql, oarcle, ...) 088 * @return the existing data sources 089 * @throws Exception if an error occurred 090 */ 091 public List<Map<String, Object>> getDataSources (DataSourceType dataSourceType, boolean includePrivate, boolean includeInternal, boolean includeDefault, List<String> allowedTypes) throws Exception 092 { 093 List<Map<String, Object>> datasources = new ArrayList<>(); 094 095 switch (dataSourceType) 096 { 097 case SQL: 098 Map<String, DataSourceDefinition> sqlDataSources = _sqlDataSourceManager.getDataSourceDefinitions(includePrivate, includeInternal, includeDefault); 099 for (String id : sqlDataSources.keySet()) 100 { 101 if (allowedTypes == null || allowedTypes.contains(sqlDataSources.get(id).getParameters().get(SQLDataSourceManager.PARAM_DATABASE_TYPE))) 102 { 103 datasources.add(getSQLDataSource(id)); 104 } 105 } 106 break; 107 108 case LDAP: 109 Map<String, DataSourceDefinition> ldapDataSources = _ldapDataSourceManager.getDataSourceDefinitions(includePrivate, includeInternal, includeDefault); 110 for (String id : ldapDataSources.keySet()) 111 { 112 datasources.add(getLDAPDataSource(id)); 113 } 114 break; 115 116 default: 117 break; 118 } 119 120 return datasources; 121 } 122 123 /** 124 * Get the existing data sources 125 * @param dataSourceType the data source type. Can be empty or null to get all data sources 126 * @param includePrivate true to include private data sources 127 * @param includeInternal true to include internal data sources 128 * @param includeDefault true to include the default data sources 129 * @param allowedTypes The sub-types of datasource allowed. Can be null. For now, this parameter is only used for sql data sources. The types are the type of databases (mysql, oarcle, ...) 130 * @return the existing data sources 131 * @throws Exception if an error occurred 132 */ 133 @Callable (rights = "Runtime_Rights_Admin_Access", context = "/admin") 134 public List<Map<String, Object>> getDataSources (String dataSourceType, boolean includePrivate, boolean includeInternal, boolean includeDefault, List<String> allowedTypes) throws Exception 135 { 136 List<Map<String, Object>> datasources = new ArrayList<>(); 137 138 if (StringUtils.isEmpty(dataSourceType) || dataSourceType.equals(DataSourceType.SQL.toString())) 139 { 140 datasources.addAll(getDataSources(DataSourceType.SQL, includePrivate, includeInternal, includeDefault, allowedTypes)); 141 } 142 143 if (StringUtils.isEmpty(dataSourceType) || dataSourceType.equals(DataSourceType.LDAP.toString())) 144 { 145 datasources.addAll(getDataSources(DataSourceType.LDAP, includePrivate, includeInternal, includeDefault, allowedTypes)); 146 } 147 148 return datasources; 149 } 150 151 /** 152 * Get the existing data sources regardless of their type 153 * @param includePrivate true to include private data sources 154 * @param includeInternal true to include internal data sources 155 * @param includeDefault true to include default data sources 156 * @return the existing data sources 157 * @throws Exception if an error occurred 158 */ 159 @Callable (rights = "Runtime_Rights_Admin_Access", context = "/admin") 160 public List<Map<String, Object>> getDataSources (boolean includePrivate, boolean includeInternal, boolean includeDefault) throws Exception 161 { 162 return getDataSources((String) null, includePrivate, includeInternal, includeDefault, null); 163 } 164 165 /** 166 * Get the ldap data source information 167 * @param type the data source's type 168 * @param id The id the data source 169 * @return The data source's information 170 * @throws Exception if an error occurred 171 */ 172 @Callable (rights = "Runtime_Rights_Admin_Access", context = "/admin") 173 public Map<String, Object> getDataSource (String type, String id) throws Exception 174 { 175 DataSourceType dsType = DataSourceType.valueOf(type); 176 switch (dsType) 177 { 178 case SQL: 179 return getSQLDataSource(id); 180 181 case LDAP: 182 return getLDAPDataSource(id); 183 default: 184 getLogger().error("Unable to get data source: unknown data source type '" + type + "'."); 185 return null; 186 } 187 } 188 189 190 /** 191 * Get the ldap data source information 192 * @param id The id the data source 193 * @return The data source's information 194 * @throws Exception if an error occurred 195 */ 196 @Callable (rights = "Runtime_Rights_Admin_Access", context = "/admin") 197 public Map<String, Object> getLDAPDataSource (String id) throws Exception 198 { 199 DataSourceDefinition ldapDefinition = _ldapDataSourceManager.getDataSourceDefinition(id); 200 Map<String, Object> def2json = _dataSourceDefinition2Json(DataSourceType.LDAP.toString(), ldapDefinition); 201 if (ldapDefinition == null) 202 { 203 getLogger().error("Unable to find the data source definition for the id '" + id + "'."); 204 } 205 else 206 { 207 def2json.put("id", id); // Keep the 'LDAP-default-datasource' id 208 def2json.put("type", "LDAP"); 209 210 Map<String, Object> parameters = new HashMap<>(); 211 parameters.putAll(ldapDefinition.getParameters()); 212 // We do not want password to travel in clear 213 if (StringUtils.isNotBlank((String) parameters.get("adminPassword"))) 214 { 215 parameters.put("adminPassword", "PASSWORD"); 216 } 217 def2json.putAll(parameters); 218 219 // The configuration data source consumer refers to the stored values of the configuration 220 // For the default data source, it is "LDAP-default-datasource" 221 boolean isInUse = TypeOfUse.merge(_dataSourceConsumerEP.isInUse(ldapDefinition.getId()), ldapDefinition.isDefault() ? _dataSourceConsumerEP.isInUse(_ldapDataSourceManager.getDefaultDataSourceId()) : TypeOfUse.NOT_USED) != TypeOfUse.NOT_USED; 222 def2json.put("isInUse", isInUse); 223 224 if ((_ldapDataSourceManager.getDataSourcePrefixId() + AbstractDataSourceManager.DEFAULT_DATASOURCE_SUFFIX).equals(id)) 225 { 226 _setDefaultDataSourceName(def2json); 227 } 228 } 229 230 return def2json; 231 } 232 233 /** 234 * Get the sql data source information 235 * @param id The id the data source 236 * @return The data source's information 237 * @throws Exception if an error occurred 238 */ 239 @Callable (rights = "Runtime_Rights_Admin_Access", context = "/admin") 240 public Map<String, Object> getSQLDataSource (String id) throws Exception 241 { 242 DataSourceDefinition sqlDefinition = _sqlDataSourceManager.getDataSourceDefinition(id); 243 244 Map<String, Object> def2json = _dataSourceDefinition2Json(DataSourceType.SQL.toString(), sqlDefinition); 245 if (sqlDefinition == null) 246 { 247 getLogger().error("Unable to find the data source definition for the id '" + id + "'."); 248 } 249 else 250 { 251 def2json.put("id", id); // Keep the 'SQL-default-datasource' id 252 def2json.put("type", "SQL"); 253 254 Map<String, Object> parameters = new HashMap<>(); 255 parameters.putAll(sqlDefinition.getParameters()); 256 // We do not want password to travel in clear 257 if (StringUtils.isNotBlank((String) parameters.get("password"))) 258 { 259 parameters.put("password", "PASSWORD"); 260 } 261 def2json.putAll(parameters); 262 263 // The configuration data source consumer refers to the stored values of the configuration 264 // For the default data source, it is "SQL-default-datasource" 265 boolean isInUse = TypeOfUse.merge(_dataSourceConsumerEP.isInUse(sqlDefinition.getId()), sqlDefinition.isDefault() ? _dataSourceConsumerEP.isInUse(_sqlDataSourceManager.getDefaultDataSourceId()) : TypeOfUse.NOT_USED) != TypeOfUse.NOT_USED; 266 def2json.put("isInUse", isInUse); 267 268 if (_sqlDataSourceManager.getDefaultDataSourceId().equals(id)) 269 { 270 _setDefaultDataSourceName(def2json); 271 } 272 } 273 274 return def2json; 275 } 276 277 /** 278 * Add a data source 279 * @param type The type of data source 280 * @param parameters The parameters of the data source to create 281 * @return the created data source as JSON object 282 * @throws IOException if an error occurred 283 * @throws SAXException if an error occurred 284 * @throws ConfigurationException if an error occurred 285 * @throws ProcessingException if an error occurred 286 */ 287 @Callable (rights = "Runtime_Rights_Admin_Access", context = "/admin") 288 public Map<String, Object> addDataSource (String type, Map<String, Object> parameters) throws ProcessingException, ConfigurationException, SAXException, IOException 289 { 290 String name = (String) parameters.get("name"); 291 String description = (String) parameters.get("description"); 292 boolean isPrivate = (boolean) parameters.get("private"); 293 294 parameters.remove("id"); 295 parameters.remove("name"); 296 parameters.remove("description"); 297 parameters.remove("private"); 298 parameters.remove("type"); 299 300 DataSourceDefinition def = null; 301 if (type.equals(DataSourceType.SQL.toString())) 302 { 303 def = _sqlDataSourceManager.add(new I18nizableText(name), new I18nizableText(description), parameters, isPrivate); 304 } 305 else if (type.equals(DataSourceType.LDAP.toString())) 306 { 307 def = _ldapDataSourceManager.add(new I18nizableText(name), new I18nizableText(description), parameters, isPrivate); 308 } 309 else 310 { 311 throw new IllegalArgumentException("Unable to add data source: unknown data source type '" + type + "'."); 312 } 313 314 return _dataSourceDefinition2Json(type, def); 315 } 316 317 /** 318 * Edit a data source 319 * @param type The type of data source 320 * @param parameters The parameters of the data source to edit 321 * @return the edited data source as JSON object 322 * @throws IOException if an error occurred 323 * @throws SAXException if an error occurred 324 * @throws ConfigurationException if an error occurred 325 * @throws ProcessingException if an error occurred 326 */ 327 @Callable (rights = "Runtime_Rights_Admin_Access", context = "/admin") 328 public Map<String, Object> editDataSource (String type, Map<String, Object> parameters) throws ProcessingException, ConfigurationException, SAXException, IOException 329 { 330 String id = (String) parameters.get("id"); 331 String name = (String) parameters.get("name"); 332 String description = (String) parameters.get("description"); 333 boolean isPrivate = (boolean) parameters.get("private"); 334 335 parameters.remove("id"); 336 parameters.remove("name"); 337 parameters.remove("description"); 338 parameters.remove("private"); 339 parameters.remove("type"); 340 341 DataSourceDefinition def = null; 342 if (type.equals(DataSourceType.SQL.toString())) 343 { 344 DataSourceDefinition previousdataSourceDefinition = _sqlDataSourceManager.getDataSourceDefinition(id); 345 if (previousdataSourceDefinition != null) 346 { 347 // Inject the recorded password before overriding the existing data source (saved passwords are not sent) 348 String previousPassword = (String) previousdataSourceDefinition.getParameters().get("password"); 349 if (parameters.get("password") == null && StringUtils.isNotEmpty(previousPassword)) 350 { 351 parameters.put("password", previousPassword); 352 } 353 } 354 else 355 { 356 getLogger().error("The data source of id '" + id + "' was not found. Unable to get the previous password."); 357 } 358 359 def = _sqlDataSourceManager.edit(id, new I18nizableText(name), new I18nizableText(description), parameters, isPrivate); 360 } 361 else if (type.equals(DataSourceType.LDAP.toString())) 362 { 363 DataSourceDefinition previousdataSourceDefinition = _ldapDataSourceManager.getDataSourceDefinition(id); 364 if (previousdataSourceDefinition != null) 365 { 366 // Inject the recorded password before overriding the existing data source (saved passwords are not sent) 367 String previousPassword = (String) previousdataSourceDefinition.getParameters().get(LDAPDataSourceManager.PARAM_ADMIN_PASSWORD); 368 if (parameters.get(LDAPDataSourceManager.PARAM_ADMIN_PASSWORD) == null && StringUtils.isNotEmpty(previousPassword)) 369 { 370 parameters.put(LDAPDataSourceManager.PARAM_ADMIN_PASSWORD, previousPassword); 371 } 372 } 373 else 374 { 375 getLogger().error("The data source of id '" + id + "' was not found. Unable to get the previous password."); 376 } 377 378 def = _ldapDataSourceManager.edit(id, new I18nizableText(name), new I18nizableText(description), parameters, isPrivate); 379 } 380 else 381 { 382 throw new IllegalArgumentException("Unable to edit data source: unknown data source type '" + type + "'."); 383 } 384 385 return _dataSourceDefinition2Json(type, def); 386 } 387 388 /** 389 * Remove one or several data sources 390 * @param type The type of data source 391 * @param ids the ids of the data sources to remove 392 * @throws IOException if an error occurred while reading configuration file 393 * @throws SAXException if an error occurred while parsing configuration file 394 * @throws ConfigurationException if an error occurred while parsing configuration reading file 395 * @throws ProcessingException if an error occurred while saving changes 396 */ 397 @Callable (rights = "Runtime_Rights_Admin_Access", context = "/admin") 398 public void removeDataSource (String type, List<String> ids) throws ConfigurationException, SAXException, IOException, ProcessingException 399 { 400 if (type.equals(DataSourceType.SQL.toString())) 401 { 402 _sqlDataSourceManager.delete(ids, false); 403 } 404 else if (type.equals(DataSourceType.LDAP.toString())) 405 { 406 _ldapDataSourceManager.delete(ids, false); 407 } 408 else 409 { 410 throw new IllegalArgumentException("Unable to delete data sources: unknown data source type '" + type + "'."); 411 } 412 } 413 414 /** 415 * Set the data source of the given id as the default data source for the given type 416 * @param type the type of the data source 417 * @param id the id of the data source 418 * @return the {@link DataSourceDefinition} of data source set as default in JSON 419 */ 420 @Callable (rights = "Runtime_Rights_Admin_Access", context = "/admin") 421 public Map<String, Object> setDefaultDataSource(String type, String id) 422 { 423 DataSourceDefinition def = null; 424 if (type.equals(DataSourceType.SQL.toString())) 425 { 426 def = _sqlDataSourceManager.setDefaultDataSource(id); 427 } 428 else if (type.equals(DataSourceType.LDAP.toString())) 429 { 430 def = _ldapDataSourceManager.setDefaultDataSource(id); 431 } 432 else 433 { 434 throw new IllegalArgumentException("Unable set to default the data source: unknown data source type '" + type + "'."); 435 } 436 437 return _dataSourceDefinition2Json(type, def); 438 } 439 440 private void _setDefaultDataSourceName(Map<String, Object> dataSourceAsJSON) 441 { 442 String defaultDataSourceName = _i18nUtils.translate(new I18nizableText("plugin.core", "PLUGINS_CORE_DEFAULT_DATASOURCE_NAME_PREFIX")); 443 defaultDataSourceName += _i18nUtils.translate((I18nizableText) dataSourceAsJSON.get("name")); 444 defaultDataSourceName += _i18nUtils.translate(new I18nizableText("plugin.core", "PLUGINS_CORE_DEFAULT_DATASOURCE_NAME_SUFFIX")); 445 446 dataSourceAsJSON.put("name", defaultDataSourceName); 447 } 448 449 private Map<String, Object> _dataSourceDefinition2Json (String type, DataSourceDefinition dataSourceDef) 450 { 451 Map<String, Object> infos = new HashMap<>(); 452 if (dataSourceDef != null) 453 { 454 infos.put("id", dataSourceDef.getId()); 455 infos.put("name", dataSourceDef.getName()); 456 infos.put("description", dataSourceDef.getDescription()); 457 infos.put("private", dataSourceDef.isPrivate()); 458 infos.put("isDefault", dataSourceDef.isDefault()); 459 460 boolean isInUse = TypeOfUse.merge(_dataSourceConsumerEP.isInUse(dataSourceDef.getId()), dataSourceDef.isDefault() ? _dataSourceConsumerEP.isInUse(_ldapDataSourceManager.getDefaultDataSourceId()) : TypeOfUse.NOT_USED) != TypeOfUse.NOT_USED; 461 infos.put("isInUse", isInUse); 462 463 Map<String, Object> parameters = dataSourceDef.getParameters(); 464 for (String paramName : parameters.keySet()) 465 { 466 infos.put(paramName, parameters.get(paramName)); 467 } 468 469 // Is the data source valid ? 470 infos.put("isValid", _isValid(type, parameters)); 471 } 472 473 return infos; 474 } 475 476 private boolean _isValid(String type, Map<String, Object> parameters) 477 { 478 boolean isValid = true; 479 if (type.equals(DataSourceType.SQL.toString())) 480 { 481 try 482 { 483 _sqlDataSourceManager.checkParameters(parameters); 484 } 485 catch (ItemCheckerTestFailureException e) 486 { 487 isValid = false; 488 } 489 } 490 else if (type.equals(DataSourceType.LDAP.toString())) 491 { 492 try 493 { 494 _ldapDataSourceManager.checkParameters(parameters); 495 } 496 catch (ItemCheckerTestFailureException e) 497 { 498 isValid = false; 499 } 500 } 501 else 502 { 503 throw new IllegalArgumentException("Unable to convert a data source definition to JSON : unknown data source type '" + type + "'."); 504 } 505 506 return isValid; 507 } 508}