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