001/* 002 * Copyright 2018 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.userdirectory.synchronize; 017 018import java.util.ArrayList; 019import java.util.Arrays; 020import java.util.HashMap; 021import java.util.HashSet; 022import java.util.List; 023import java.util.Map; 024import java.util.Set; 025 026import javax.jcr.RepositoryException; 027 028import org.apache.avalon.framework.service.ServiceException; 029import org.apache.avalon.framework.service.ServiceManager; 030import org.apache.commons.collections4.MapUtils; 031import org.apache.commons.lang.StringUtils; 032import org.apache.commons.lang3.ArrayUtils; 033import org.slf4j.Logger; 034 035import org.ametys.cms.ObservationConstants; 036import org.ametys.cms.content.external.ExternalizableMetadataHelper; 037import org.ametys.cms.content.references.OutgoingReferencesExtractor; 038import org.ametys.cms.repository.Content; 039import org.ametys.cms.repository.ContentQueryHelper; 040import org.ametys.cms.repository.ContentTypeExpression; 041import org.ametys.cms.repository.LanguageExpression; 042import org.ametys.cms.repository.ModifiableContent; 043import org.ametys.cms.repository.ModifiableDefaultContent; 044import org.ametys.cms.repository.WorkflowAwareContent; 045import org.ametys.cms.workflow.ContentWorkflowHelper; 046import org.ametys.core.observation.Event; 047import org.ametys.core.user.CurrentUserProvider; 048import org.ametys.plugins.contentio.synchronize.expression.CollectionExpression; 049import org.ametys.plugins.contentio.synchronize.impl.SQLSynchronizableContentsCollection; 050import org.ametys.plugins.repository.AmetysObjectIterable; 051import org.ametys.plugins.repository.AmetysObjectIterator; 052import org.ametys.plugins.repository.RepositoryConstants; 053import org.ametys.plugins.repository.UnknownAmetysObjectException; 054import org.ametys.plugins.repository.jcr.DefaultAmetysObject; 055import org.ametys.plugins.repository.metadata.ModifiableCompositeMetadata; 056import org.ametys.plugins.repository.query.expression.AndExpression; 057import org.ametys.plugins.repository.query.expression.Expression; 058import org.ametys.plugins.repository.query.expression.Expression.Operator; 059import org.ametys.plugins.repository.query.expression.StringExpression; 060import org.ametys.plugins.userdirectory.DeleteOrgUnitComponent; 061import org.ametys.plugins.userdirectory.OrganisationChartPageHandler; 062import org.ametys.plugins.userdirectory.UserDirectoryHelper; 063import org.ametys.plugins.userdirectory.UserDirectoryPageHandler; 064import org.ametys.plugins.userdirectory.expression.RemoteIdOrgunitExpression; 065import org.ametys.runtime.i18n.I18nizableText; 066 067import com.google.common.collect.Maps; 068import com.opensymphony.workflow.WorkflowException; 069 070/** 071 * Synchronizable collection for UD Orgunits 072 */ 073public class SQLSynchronizableUDOrgunitCollection extends SQLSynchronizableContentsCollection 074{ 075 /** The internal metadata name for orgUnit remote sql id */ 076 public static final String ORGUNIT_REMOTE_ID_INTERNAL_METADATA = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":orgunit-remote-id"; 077 078 private static final String __PARAM_SQL_TABLE_USER = "tableNameUser"; 079 private static final String __PARAM_SQL_ORGUNIT_JOIN_COLUMN_NAME = "orgUnitJoinColumnName"; 080 private static final String __PARAM_LOGIN_USER_METADATA_NAME = "loginUser"; 081 private static final String __PARAM_SQL_LOGIN_USER_COLUMN_NAME = "loginColumnName"; 082 private static final String __PARAM_SQL_ROLE_USER_COLUMN_NAME = "roleColumnName"; 083 private static final String __PARAM_SQL_ORGUNIT_REMOTE_ID_COLUMN_NAME = "orgunitRemoteIdColumnName"; 084 085 /** The map which link orgunit with this parent */ 086 protected Map<String, String> _orgUnitParents; 087 088 /** The map which link orgunit with users (userId and role) */ 089 protected Map<String, Map<String, String>> _orgUnitUsers; 090 091 /** The map which link orgunit with sql remote ids */ 092 protected Map<String, String> _orgUnitRemoteIds; 093 094 /** The set of orgunits to apply changes */ 095 protected Set<WorkflowAwareContent> _orgUnitsToApplyChanges; 096 097 /** The sql user search DAO */ 098 protected SQLUserSearchDAO _sqlUserDAO; 099 100 /** The organisation chart page handler */ 101 protected OrganisationChartPageHandler _orgChartPageHandler; 102 103 /** The content workflow helper */ 104 protected ContentWorkflowHelper _contentWorkflowHelper; 105 106 /** The current user provider */ 107 protected CurrentUserProvider _userProvider; 108 109 /** The OutgoingReferences extractor */ 110 protected OutgoingReferencesExtractor _outgoingReferencesExtractor; 111 112 /** The delete orgUnit component */ 113 protected DeleteOrgUnitComponent _deleteOrgUnitComponent; 114 115 @Override 116 public void service(ServiceManager smanager) throws ServiceException 117 { 118 super.service(smanager); 119 _sqlUserDAO = (SQLUserSearchDAO) smanager.lookup(SQLUserSearchDAO.ROLE); 120 _orgChartPageHandler = (OrganisationChartPageHandler) smanager.lookup(OrganisationChartPageHandler.ROLE); 121 _contentWorkflowHelper = (ContentWorkflowHelper) smanager.lookup(ContentWorkflowHelper.ROLE); 122 _userProvider = (CurrentUserProvider) smanager.lookup(CurrentUserProvider.ROLE); 123 _outgoingReferencesExtractor = (OutgoingReferencesExtractor) smanager.lookup(OutgoingReferencesExtractor.ROLE); 124 _deleteOrgUnitComponent = (DeleteOrgUnitComponent) smanager.lookup(DeleteOrgUnitComponent.ROLE); 125 } 126 127 /** 128 * Get the name of user SQL table 129 * @return The user SQL table name 130 */ 131 public String getUserTableName() 132 { 133 return (String) getParameterValues().get(__PARAM_SQL_TABLE_USER); 134 } 135 136 /** 137 * Get the name of the orgunit join column name of the user table 138 * @return The name of the orgunit join column name 139 */ 140 public String getOrgunitJoinColumnNameForUser() 141 { 142 return (String) getParameterValues().get(__PARAM_SQL_ORGUNIT_JOIN_COLUMN_NAME); 143 } 144 145 /** 146 * Get the login user metadata name 147 * @return The login user metadata name 148 */ 149 public String getLoginUserMetadataName() 150 { 151 return (String) getParameterValues().get(__PARAM_LOGIN_USER_METADATA_NAME); 152 } 153 154 /** 155 * Get the login user column name 156 * @return The login user column name 157 */ 158 public String getLoginUserColumnName() 159 { 160 return (String) getParameterValues().get(__PARAM_SQL_LOGIN_USER_COLUMN_NAME); 161 } 162 163 /** 164 * Get the role user column name 165 * @return The role user column name 166 */ 167 public String getRoleUserColumnName() 168 { 169 return (String) getParameterValues().get(__PARAM_SQL_ROLE_USER_COLUMN_NAME); 170 } 171 172 /** 173 * Get the orgunit remote id column name 174 * @return The orgunit remote id column name 175 */ 176 public String getOrgUnitRemoteIdColumnName() 177 { 178 return (String) getParameterValues().get(__PARAM_SQL_ORGUNIT_REMOTE_ID_COLUMN_NAME); 179 } 180 181 @Override 182 protected Map<String, Object> _getSearchParameters(Map<String, Object> parameters, int offset, int limit, List<Object> sort, List<String> columns) 183 { 184 // Add the sql column name for the orgunit id. 185 // It's for setting the ametys-internal:orgunit-remote-id and retrieve easily the orgUnit content with this Id 186 String orgUnitIdColumnName = getOrgUnitRemoteIdColumnName(); 187 if (!columns.contains(orgUnitIdColumnName)) 188 { 189 columns.add(orgUnitIdColumnName); 190 } 191 192 return super._getSearchParameters(parameters, offset, limit, sort, columns); 193 } 194 195 @Override 196 protected Map<String, Map<String, Object>> internalSearch(Map<String, Object> parameters, int offset, int limit, List<Object> sort, Logger logger) 197 { 198 Map<String, Map<String, Object>> internalSearch = super.internalSearch(parameters, offset, limit, sort, logger); 199 200 // Fill _orgUnitRemoteIds and _orgUnitParents maps with search results 201 String orgUnitRemoteIdColumnName = getOrgUnitRemoteIdColumnName(); 202 for (String orgUnitIdValue : internalSearch.keySet()) 203 { 204 Map<String, Object> orgUnitValues = internalSearch.get(orgUnitIdValue); 205 _orgUnitRemoteIds.put(orgUnitIdValue, orgUnitValues.get(orgUnitRemoteIdColumnName).toString()); 206 207 Map<String, List<String>> mapping = getMapping(); 208 if (mapping.containsKey(OrganisationChartPageHandler.PARENT_ORGUNIT_ATTRIBUTE_NAME)) 209 { 210 // We take the first because it's no sense to defined two sql columns to define orgunit parents 211 String parentColumn = mapping.get(OrganisationChartPageHandler.PARENT_ORGUNIT_ATTRIBUTE_NAME).get(0); 212 if (orgUnitValues.containsKey(parentColumn) && StringUtils.isNotBlank(orgUnitValues.get(parentColumn).toString())) 213 { 214 _orgUnitParents.put(orgUnitIdValue, orgUnitValues.get(parentColumn).toString()); 215 } 216 } 217 } 218 219 return internalSearch; 220 } 221 222 @Override 223 public List<ModifiableDefaultContent> populate(Logger logger) 224 { 225 List<ModifiableDefaultContent> populateContents = super.populate(logger); 226 logger.info("{} contents have been modified to update the parent-child relations.", _orgUnitsToApplyChanges.size()); 227 228 return populateContents; 229 } 230 231 @Override 232 public List<ModifiableDefaultContent> _internalPopulate(Logger logger) 233 { 234 _orgUnitParents = new HashMap<>(); 235 _orgUnitRemoteIds = new HashMap<>(); 236 _orgUnitUsers = _getOrgUnitUser(logger); 237 _orgUnitsToApplyChanges = new HashSet<>(); 238 239 List<ModifiableDefaultContent> contents = super._internalPopulate(logger); 240 241 //All orgunits are imported, now we can set relation with parent orgunit 242 _setContentsRelationWithParentOrgunit(contents, logger); 243 244 for (WorkflowAwareContent content : _orgUnitsToApplyChanges) 245 { 246 try 247 { 248 I18nizableText comment = new I18nizableText("plugin.user-directory", "PLUGINS_USER_DIRECTORY_WORKFLOW_ACTION_EDIT_ORGUNIT_REFERENCE_MSG"); 249 _applyChanges(content, getSynchronizeActionId(), _i18nUtils.translate(comment, content.getLanguage())); 250 } 251 catch (WorkflowException e) 252 { 253 logger.error("Can't apply change to content : " + content.getId()); 254 } 255 } 256 257 return contents; 258 } 259 260 @Override 261 protected List<ModifiableDefaultContent> _importOrSynchronizeContent(String idValue, String lang, Map<String, List<Object>> remoteValues, boolean forceImport, Logger logger) 262 { 263 remoteValues.remove(OrganisationChartPageHandler.PARENT_ORGUNIT_ATTRIBUTE_NAME); 264 265 List<ModifiableDefaultContent> contents = super._importOrSynchronizeContent(idValue, lang, remoteValues, forceImport, logger); 266 for (ModifiableDefaultContent content : contents) 267 { 268 try 269 { 270 content.getNode().setProperty(ORGUNIT_REMOTE_ID_INTERNAL_METADATA, _orgUnitRemoteIds.get(idValue)); 271 } 272 catch (RepositoryException e) 273 { 274 _nbError++; 275 logger.error("An error occurred while importing or synchronizing content", e); 276 } 277 } 278 279 return contents; 280 } 281 282 @Override 283 protected boolean additionalSynchronizeOperations(ModifiableDefaultContent content, Map<String, List<Object>> remoteValues, Logger logger) 284 { 285 boolean hasChanges = super.additionalSynchronizeOperations(content, remoteValues, logger); 286 return _additionalOperations(content, remoteValues, logger) || hasChanges; 287 } 288 289 @Override 290 protected boolean additionalImportOperations(ModifiableDefaultContent content, Map<String, List<Object>> remoteValues, Map<String, Object> importParams, Logger logger) 291 { 292 boolean hasChanges = super.additionalImportOperations(content, remoteValues, importParams, logger); 293 return _additionalOperations(content, remoteValues, logger) || hasChanges; 294 } 295 296 /** 297 * Do additional operations 298 * @param orgUnit the orgUnit content 299 * @param remoteValues the remote values 300 * @param logger the logger 301 * @return true if changes 302 */ 303 protected boolean _additionalOperations(ModifiableDefaultContent orgUnit, Map<String, List<Object>> remoteValues, Logger logger) 304 { 305 boolean hasChanges = false; 306 307 String orgUnitIdValue = _getIdFieldValue(orgUnit); 308 try 309 { 310 hasChanges = _synchronizeUserRepeaterOperation(orgUnit, orgUnitIdValue, logger); 311 } 312 catch (Exception e) 313 { 314 logger.error("An error occurred synchronizing user repeater for orgunit " + orgUnit.getId(), e); 315 } 316 317 return hasChanges; 318 } 319 320 /** 321 * Get orgUnit user map 322 * @param logger the logger 323 * @return the orgUnit user map 324 */ 325 protected Map<String, Map<String, String>> _getOrgUnitUser(Logger logger) 326 { 327 Map<String, Map<String, String>> orgUnitUsers = new HashMap<>(); 328 329 Map<String, List<String>> mapping = getMapping(); 330 String idField = getIdField(); 331 List<String> remoteOrgUnitKeys = mapping.get(idField); 332 if (remoteOrgUnitKeys != null && remoteOrgUnitKeys.size() > 0) 333 { 334 String remoteOrgUnitKey = remoteOrgUnitKeys.get(0); 335 336 Map<String, Object> userParameters = _getSearchUserParameters(remoteOrgUnitKey, logger); 337 List<Map<String, Object>> searchUserList = _sqlUserDAO.searchUser(userParameters, getDataSourceId()); 338 339 String loginColumnName = getLoginUserColumnName(); 340 String roleColumnName = getRoleUserColumnName(); 341 342 List<String> userColumns = new ArrayList<>(); 343 userColumns.add(loginColumnName); 344 userColumns.add(remoteOrgUnitKey); 345 if (StringUtils.isNotBlank(roleColumnName)) 346 { 347 userColumns.add(roleColumnName); 348 } 349 350 for (Map<String, Object> userMap : searchUserList) 351 { 352 Map<String, Object> normalizedUserMap = _getNormalizedSearchResult(userColumns, userMap); 353 _fillOrgUnitUserMap(orgUnitUsers, remoteOrgUnitKey, loginColumnName, roleColumnName, normalizedUserMap, logger); 354 } 355 } 356 357 return orgUnitUsers; 358 } 359 360 /** 361 * Fill the orgUnitUsers map 362 * @param orgUnitUsers the orgunit user map 363 * @param remoteOrgUnitKey the remote key 364 * @param loginColumnName the login column name 365 * @param roleColumnName the role column name 366 * @param normalizedUserMap the normalized search user map 367 * @param logger the logger 368 */ 369 protected void _fillOrgUnitUserMap(Map<String, Map<String, String>> orgUnitUsers, String remoteOrgUnitKey, String loginColumnName, String roleColumnName, Map<String, Object> normalizedUserMap, Logger logger) 370 { 371 String loginValue = (normalizedUserMap.get(loginColumnName) == null) ? null : String.valueOf(normalizedUserMap.get(loginColumnName)); 372 String orgUnitIdValue = normalizedUserMap.get(remoteOrgUnitKey).toString(); 373 374 if (StringUtils.isNotBlank(loginValue)) 375 { 376 String roleValue = null; 377 if (StringUtils.isNotBlank(roleColumnName)) 378 { 379 roleValue = (normalizedUserMap.get(roleColumnName) == null) ? null : String.valueOf(normalizedUserMap.get(roleColumnName)); 380 } 381 382 if (!orgUnitUsers.containsKey(orgUnitIdValue)) 383 { 384 orgUnitUsers.put(orgUnitIdValue, Maps.newHashMap()); 385 } 386 387 Map<String, String> orgUnitUserMap = orgUnitUsers.get(orgUnitIdValue); 388 orgUnitUserMap.put(loginValue, roleValue); 389 } 390 else 391 { 392 logger.warn("Can't add user to orgunit '" + orgUnitIdValue + "' because the login value is blank ..."); 393 } 394 } 395 396 /** 397 * Set all orgunit parents relation for each synchronized content 398 * @param orgUnitContents the synchronized content 399 * @param logger the logger 400 */ 401 protected void _setContentsRelationWithParentOrgunit(List<ModifiableDefaultContent> orgUnitContents, Logger logger) 402 { 403 for (ModifiableDefaultContent orgUnitContent : orgUnitContents) 404 { 405 String orgUnitIdValue = _getIdFieldValue(orgUnitContent); 406 String parentIdSQL = _orgUnitParents.get(orgUnitIdValue); 407 408 Content parentContent = _orgChartPageHandler.getParentContent(orgUnitContent); 409 Content newParentContent = _getOrgUnitContentFromRemoteId(parentIdSQL, orgUnitContent.getLanguage(), logger); 410 411 if (StringUtils.isNotBlank(parentIdSQL) && newParentContent == null) 412 { 413 logger.warn("The orgUnit with sql id '" + parentIdSQL + "' doesn't exist. So this orgUnit is consider as null"); 414 } 415 416 //We set the relation if parentContent and newParentContent is not the same 417 if (!(parentContent == null && newParentContent == null 418 || (parentContent != null && newParentContent != null && StringUtils.equals(parentContent.getId(), newParentContent.getId())))) 419 { 420 try 421 { 422 _setRelationWithParentOrgunit((ModifiableDefaultContent) newParentContent, orgUnitContent, logger); 423 } 424 catch (Exception e) 425 { 426 _nbError++; 427 String newParentId = newParentContent != null ? newParentContent.getId() : StringUtils.EMPTY; 428 logger.error("Can't set parent relation between the parent '" + newParentId + "' and the child '" + orgUnitContent.getId() + "'.", e); 429 } 430 } 431 } 432 } 433 434 /** 435 * Set the relation between the orgunit parent and its child 436 * @param parentContent the parent content 437 * @param childContent the child content 438 * @param logger the logger 439 * @throws WorkflowException if an error occurred 440 */ 441 protected void _setRelationWithParentOrgunit(ModifiableDefaultContent parentContent, ModifiableDefaultContent childContent, Logger logger) throws WorkflowException 442 { 443 ModifiableCompositeMetadata childMetadataHolder = childContent.getMetadataHolder(); 444 String parentOrgUnitId = childMetadataHolder.getString(OrganisationChartPageHandler.PARENT_ORGUNIT_ATTRIBUTE_NAME, null); 445 446 if (StringUtils.isNotBlank(parentOrgUnitId)) 447 { 448 // We remove relation from old parent 449 ModifiableDefaultContent oldParentContent = _resolver.resolveById(parentOrgUnitId); 450 ModifiableCompositeMetadata oldParentMetadataHolder = oldParentContent.getMetadataHolder(); 451 452 String[] oldChildOrgUnit = oldParentMetadataHolder.getStringArray(OrganisationChartPageHandler.CHILD_ORGUNIT_ATTRIBUTE_NAME, ArrayUtils.EMPTY_STRING_ARRAY); 453 454 String[] newValues = ArrayUtils.removeElement(oldChildOrgUnit, childContent.getId()); 455 ExternalizableMetadataHelper.setMetadata(oldParentMetadataHolder, OrganisationChartPageHandler.CHILD_ORGUNIT_ATTRIBUTE_NAME, newValues); 456 457 _orgUnitsToApplyChanges.add(oldParentContent); 458 } 459 460 if (parentContent == null) 461 { 462 // The parent content is null, so just remove parent metadata 463 childContent.getMetadataHolder().removeMetadata(OrganisationChartPageHandler.PARENT_ORGUNIT_ATTRIBUTE_NAME); 464 _orgUnitsToApplyChanges.add(childContent); 465 } 466 else 467 { 468 // Change child orgunit metadata for parent content 469 ModifiableCompositeMetadata parentMetadataHolder = parentContent.getMetadataHolder(); 470 String[] childOrgUnit = parentMetadataHolder.getStringArray(OrganisationChartPageHandler.CHILD_ORGUNIT_ATTRIBUTE_NAME, ArrayUtils.EMPTY_STRING_ARRAY); 471 472 String[] newValues = ArrayUtils.add(childOrgUnit, childContent.getId()); 473 ExternalizableMetadataHelper.setMetadata(parentMetadataHolder, OrganisationChartPageHandler.CHILD_ORGUNIT_ATTRIBUTE_NAME, newValues); 474 _orgUnitsToApplyChanges.add(parentContent); 475 476 // Change parent orgunit metadata for child content 477 childMetadataHolder.setMetadata(OrganisationChartPageHandler.PARENT_ORGUNIT_ATTRIBUTE_NAME, parentContent.getId()); 478 _orgUnitsToApplyChanges.add(childContent); 479 } 480 } 481 482 private void _applyChanges(WorkflowAwareContent content, int actionId, String comment) throws WorkflowException 483 { 484 content.saveChanges(); 485 ((DefaultAmetysObject) content).checkpoint(); 486 487 // Notify listeners 488 Map<String, Object> eventParams = new HashMap<>(); 489 eventParams.put(ObservationConstants.ARGS_CONTENT, content); 490 eventParams.put(ObservationConstants.ARGS_CONTENT_ID, content.getId()); 491 492 _observationManager.notify(new Event(ObservationConstants.EVENT_CONTENT_MODIFIED, _userProvider.getUser(), eventParams)); 493 494 Map<String, Object> inputs = new HashMap<>(); 495 inputs.put("comment", comment); 496 _contentWorkflowHelper.doAction(content, actionId, inputs); 497 } 498 499 /** 500 * Synchronize user repeater for orgunit content 501 * @param orgUnitContent the orgunit content 502 * @param orgUnitIdValue the orgUnit id value 503 * @param logger the logger 504 * @return true if changes 505 * @throws WorkflowException if an error occurred 506 */ 507 protected boolean _synchronizeUserRepeaterOperation(ModifiableDefaultContent orgUnitContent, String orgUnitIdValue, Logger logger) throws WorkflowException 508 { 509 boolean hasChanges = false; 510 511 Map<String, String> orgUnitUsers = _orgUnitUsers.get(orgUnitIdValue); 512 if (orgUnitUsers != null) 513 { 514 hasChanges = _synchronizeUserRepeater(orgUnitContent, orgUnitUsers, logger); 515 } 516 else if (orgUnitContent.hasValue(OrganisationChartPageHandler.ORGUNIT_USERS_ATTRIBUTE_NAME)) 517 { 518 orgUnitContent.removeValue(OrganisationChartPageHandler.ORGUNIT_USERS_ATTRIBUTE_NAME); 519 } 520 521 return hasChanges; 522 } 523 524 // TODO wait content metadata is handled in the new API 525 // CMS-9483 Implement new API for content metadata 526// /** 527// * True if the user repeater needs changes 528// * @param orgUnitContent the orgUnit content 529// * @param orgUnitUsers the map of orgunit users 530// * @param logger the logger 531// * @return true if the user repeater needs changes 532// * @throws WorkflowException if an error occurred 533// */ 534// protected boolean _synchronizeUserRepeater(ModifiableDefaultContent orgUnitContent, Map<String, String> orgUnitUsers, Logger logger) throws WorkflowException 535// { 536// boolean hasChanges = false; 537// String roleUserColumnName = getRoleUserColumnName(); 538// 539// ModifiableModelAwareRepeater userRepeater = orgUnitContent.getRepeater(OrganisationChartPageHandler.METADATA_ORGUNIT_USERS, true); 540// 541// int position = 1; 542// List<String> repeaterLogins = new ArrayList<>(); 543// // Compare with existing user entries 544// for (ModifiableModelAwareRepeaterEntry repeaterEntry : userRepeater.getEntries()) 545// { 546// String userId = repeaterEntry.getValue(OrganisationChartPageHandler.METADATA_ORGUNIT_USER, false, null); 547// String role = repeaterEntry.getValue(OrganisationChartPageHandler.METADATA_ORGUNIT_USER_ROLE, false, null); 548// 549// // The user id is existing 550// if (StringUtils.isNotBlank(userId)) 551// { 552// try 553// { 554// ModifiableDefaultContent userContent = _resolver.resolveById(userId); 555// String userIdValue = _getUserIdValue(userContent, logger); 556// // The user id belong to the user to synchronize and its role has changed 557// if (StringUtils.isNotBlank(roleUserColumnName) && orgUnitUsers.containsKey(userIdValue) && !StringUtils.equals(role, orgUnitUsers.get(userIdValue))) 558// { 559// String newRole = orgUnitUsers.get(userIdValue); 560// if (StringUtils.isNotBlank(newRole)) 561// { 562// repeaterEntry.setValue(OrganisationChartPageHandler.METADATA_ORGUNIT_USER_ROLE, newRole); 563// } 564// else 565// { 566// repeaterEntry.removeValue(OrganisationChartPageHandler.METADATA_ORGUNIT_USER_ROLE); 567// } 568// hasChanges = true; 569// } 570// 571// // Fix relation between orgUnit and users 572// List<String> orgunitIds = Arrays.asList(userContent.getMetadataHolder().getStringArray(UserDirectoryHelper.ORGUNITS_METADATA, ArrayUtils.EMPTY_STRING_ARRAY)); 573// if (!orgunitIds.contains(orgUnitContent.getId())) 574// { 575// _setInvertRelationForUser(orgUnitContent, userContent); 576// hasChanges = true; 577// } 578// 579// repeaterLogins.add(userIdValue); 580// } 581// catch (UnknownAmetysObjectException e) 582// { 583// // The user is not find 584// logger.warn("Can't find the content user in the repeater with id '" + userId + "'. So the entry will be removed", e); 585// userRepeater.removeEntry(position); 586// hasChanges = true; 587// } 588// } 589// else // no user id 590// { 591// logger.warn("The user content is no defined in the repeater for orgUnit id '" + orgUnitContent.getId() + "'. So the entry will be removed"); 592// userRepeater.removeEntry(position); 593// hasChanges = true; 594// } 595// 596// position++; 597// } 598// 599// String lang = orgUnitContent.getLanguage(); 600// for (String login : orgUnitUsers.keySet()) 601// { 602// if (!repeaterLogins.contains(login)) 603// { 604// Content userContent = _getUserContent(login, lang, logger); 605// if (userContent != null) 606// { 607// ModifiableModelAwareRepeaterEntry newEntry = userRepeater.addEntry(); 608// newEntry.setValue(OrganisationChartPageHandler.METADATA_ORGUNIT_USER, userContent.getId()); 609// 610// _setInvertRelationForUser(orgUnitContent, userContent); 611// 612// String role = orgUnitUsers.get(login); 613// if (StringUtils.isNotBlank(role)) 614// { 615// newEntry.setValue(OrganisationChartPageHandler.METADATA_ORGUNIT_USER_ROLE, role); 616// } 617// 618// hasChanges = true; 619// } 620// else 621// { 622// logger.warn("Can't add user '" + login + "' to orgunit '" + orgUnitContent.getTitle() + "' because he doesn't exist"); 623// } 624// } 625// } 626// 627// return hasChanges; 628// } 629 630 /** 631 * True if the user repeater needs changes 632 * @param orgUnitContent the orgUnit content 633 * @param orgUnitUsers the map of orgunit users 634 * @param logger the logger 635 * @return true if the user repeater needs changes 636 * @throws WorkflowException if an error occurred 637 */ 638 protected boolean _synchronizeUserRepeater(ModifiableDefaultContent orgUnitContent, Map<String, String> orgUnitUsers, Logger logger) throws WorkflowException 639 { 640 boolean hasChanges = false; 641 String roleUserColumnName = getRoleUserColumnName(); 642 643 ModifiableCompositeMetadata userRepeater = orgUnitContent.getMetadataHolder().getCompositeMetadata(OrganisationChartPageHandler.ORGUNIT_USERS_ATTRIBUTE_NAME, true); 644 645 int position = 1; 646 List<String> repeaterLogins = new ArrayList<>(); 647 String[] metadataNames = userRepeater.getMetadataNames(); 648 649 // We need to reverse the metadata names array because when we remove an entry and shift next entries, we don't disrupt the loop. 650 ArrayUtils.reverse(metadataNames); 651 652 // Compare with existing user entries 653 for (String metadataName : metadataNames) 654 { 655 ModifiableCompositeMetadata entry = userRepeater.getCompositeMetadata(metadataName); 656 657 String userId = entry.getString(OrganisationChartPageHandler.ORGUNIT_USER_ATTRIBUTE_NAME, null); 658 String role = entry.getString(OrganisationChartPageHandler.ORGUNIT_USER_ROLE_ATTRIBUTE_NAME, null); 659 660 // The user id is existing 661 if (StringUtils.isNotBlank(userId)) 662 { 663 try 664 { 665 ModifiableDefaultContent userContent = _resolver.resolveById(userId); 666 String userIdValue = _getUserIdValue(userContent, logger); 667 668 if (!orgUnitUsers.containsKey(userIdValue)) 669 { 670 removeEntry(Integer.valueOf(metadataName), userRepeater); 671 hasChanges = true; 672 position--; 673 } 674 else if (StringUtils.isNotBlank(roleUserColumnName) && !StringUtils.equals(role, orgUnitUsers.get(userIdValue))) 675 { 676 // The user id belong to the user to synchronize and its role has changed 677 String newRole = orgUnitUsers.get(userIdValue); 678 if (StringUtils.isNotBlank(newRole)) 679 { 680 entry.setMetadata(OrganisationChartPageHandler.ORGUNIT_USER_ROLE_ATTRIBUTE_NAME, newRole); 681 } 682 else 683 { 684 entry.removeMetadata(OrganisationChartPageHandler.ORGUNIT_USER_ROLE_ATTRIBUTE_NAME); 685 } 686 hasChanges = true; 687 } 688 689 // Fix relation between orgUnit and users 690 List<String> orgunitIds = Arrays.asList(userContent.getMetadataHolder().getStringArray(UserDirectoryHelper.ORGUNITS_METADATA, ArrayUtils.EMPTY_STRING_ARRAY)); 691 if (!orgunitIds.contains(orgUnitContent.getId())) 692 { 693 _setInvertRelationForUser(orgUnitContent, userContent); 694 hasChanges = true; 695 } 696 697 repeaterLogins.add(userIdValue); 698 } 699 catch (UnknownAmetysObjectException e) 700 { 701 // The user is not found 702 logger.warn("Can't find the content user in the repeater with id '" + userId + "'. So the entry will be removed", e); 703 removeEntry(Integer.valueOf(metadataName), userRepeater); 704 hasChanges = true; 705 position--; 706 } 707 } 708 else // no user id 709 { 710 logger.warn("The user content is no defined in the repeater for orgUnit id '" + orgUnitContent.getId() + "'. So the entry will be removed"); 711 removeEntry(Integer.valueOf(metadataName), userRepeater); 712 hasChanges = true; 713 position--; 714 } 715 716 position++; 717 } 718 719 String lang = orgUnitContent.getLanguage(); 720 for (String login : orgUnitUsers.keySet()) 721 { 722 if (!repeaterLogins.contains(login)) 723 { 724 Content userContent = _getUserContent(login, lang, logger); 725 if (userContent != null) 726 { 727 ModifiableCompositeMetadata newEntry = userRepeater.getCompositeMetadata(String.valueOf(position), true); 728 newEntry.setMetadata(OrganisationChartPageHandler.ORGUNIT_USER_ATTRIBUTE_NAME, userContent.getId()); 729 730 _setInvertRelationForUser(orgUnitContent, userContent); 731 732 String role = orgUnitUsers.get(login); 733 if (StringUtils.isNotBlank(role)) 734 { 735 newEntry.setMetadata(OrganisationChartPageHandler.ORGUNIT_USER_ROLE_ATTRIBUTE_NAME, role); 736 } 737 738 hasChanges = true; 739 position++; 740 } 741 else 742 { 743 logger.warn("Can't add user '" + login + "' to orgunit '" + orgUnitContent.getTitle() + "' because he doesn't exist"); 744 } 745 } 746 } 747 748 return hasChanges; 749 } 750 751 /** 752 * Set invert relation for user 753 * @param orgUnitContent the orgUnit content 754 * @param userContent the user content 755 * @throws WorkflowException if an error occurred 756 */ 757 protected void _setInvertRelationForUser(ModifiableDefaultContent orgUnitContent, Content userContent) throws WorkflowException 758 { 759 ModifiableCompositeMetadata metadataHolder = ((ModifiableContent) userContent).getMetadataHolder(); 760 List<String> orgunitIds = new ArrayList<>(Arrays.asList(metadataHolder.getStringArray(UserDirectoryHelper.ORGUNITS_METADATA, ArrayUtils.EMPTY_STRING_ARRAY))); 761 if (!orgunitIds.contains(orgUnitContent.getId())) 762 { 763 orgunitIds.add(orgUnitContent.getId()); 764 } 765 metadataHolder.setMetadata(UserDirectoryHelper.ORGUNITS_METADATA, orgunitIds.toArray(new String[orgunitIds.size()])); 766 767 I18nizableText comment = new I18nizableText("plugin.user-directory", "PLUGINS_USER_DIRECTORY_WORKFLOW_ACTION_EDIT_USER_REFERENCE_MSG"); 768 _applyChanges((WorkflowAwareContent) userContent, getSynchronizeActionId(), _i18nUtils.translate(comment, userContent.getLanguage())); 769 770 } 771 772 /** 773 * Removes the repeater entry at the given position. The position starts at index 1. 774 * The position can be an integer between 1 and the repeater size to remove an entry from the beginning 775 * Or the position can an integer between 0 and - the repeater size to remove an entry from the end (0 means at the end, -1 means before the last one and so on) 776 * @param position The position of the entry to remove 777 * @param repositoryComposite the repository composite of the repeater 778 * @throws IllegalArgumentException if the position is not between the negative and positive repeater size 779 */ 780 protected void removeEntry(int position, ModifiableCompositeMetadata repositoryComposite) 781 { 782 int size = repositoryComposite.getMetadataNames().length; 783 if (1 <= position && position <= size) 784 { 785 // remove the entry 786 repositoryComposite.removeMetadata(String.valueOf(position)); 787 788 // rename all entries after the removed one 789 for (int currentEntryPosition = position + 1; currentEntryPosition <= size; currentEntryPosition++) 790 { 791 ModifiableCompositeMetadata entryRepositoryComposite = repositoryComposite.getCompositeMetadata(String.valueOf(currentEntryPosition)); 792 entryRepositoryComposite.rename(RepositoryConstants.NAMESPACE_PREFIX + ":" + String.valueOf(currentEntryPosition - 1)); 793 } 794 } 795 else if (-size < position && position <= 0) 796 { 797 // Find the positive equivalent position and call the removeEntry method with this position 798 removeEntry(size + position, repositoryComposite); 799 } 800 else 801 { 802 throw new IllegalArgumentException("The repeater has '" + size + "' entries. You can not remove an entry at position '" + position + "'."); 803 } 804 } 805 806 /** 807 * Get user content from login value 808 * @param loginValue the login value 809 * @param lang the language 810 * @param logger the logger 811 * @return the user content 812 */ 813 protected Content _getUserContent(String loginValue, String lang, Logger logger) 814 { 815 String loginMetadata = getLoginUserMetadataName(); 816 Set<String> contentTypes = _contentTypeEP.getSubTypes(UserDirectoryPageHandler.ABSTRACT_USER_CONTENT_TYPE); 817 818 Expression ctypeExpression = new ContentTypeExpression(Operator.EQ, contentTypes.toArray(new String[contentTypes.size()])); 819 LanguageExpression languageExpression = new LanguageExpression(Operator.EQ, lang); 820 StringExpression metadataExp = new StringExpression(loginMetadata, Operator.EQ, loginValue); 821 822 Expression userExp = new AndExpression(metadataExp, ctypeExpression, languageExpression); 823 String xPathQuery = ContentQueryHelper.getContentXPathQuery(userExp); 824 825 AmetysObjectIterable<Content> contentQuery = _resolver.query(xPathQuery); 826 AmetysObjectIterator<Content> contentIterator = contentQuery.iterator(); 827 if (contentIterator.hasNext()) 828 { 829 return contentIterator.next(); 830 } 831 832 return null; 833 } 834 835 /** 836 * Get orgunit content from the remote Id 837 * @param remoteId the remote Id 838 * @param lang the language 839 * @param logger the logger 840 * @return the orgunit content 841 */ 842 protected Content _getOrgUnitContentFromRemoteId(String remoteId, String lang, Logger logger) 843 { 844 Expression ctypeExpression = new ContentTypeExpression(Operator.EQ, OrganisationChartPageHandler.ORGUNIT_CONTENT_TYPE); 845 RemoteIdOrgunitExpression remoteIdOrgunitExpression = new RemoteIdOrgunitExpression(remoteId); 846 LanguageExpression languageExpression = new LanguageExpression(Operator.EQ, lang); 847 848 CollectionExpression collectionExpression = new CollectionExpression(getId()); 849 850 Expression userExp = new AndExpression(remoteIdOrgunitExpression, ctypeExpression, languageExpression, collectionExpression); 851 String xPathQuery = ContentQueryHelper.getContentXPathQuery(userExp); 852 853 AmetysObjectIterable<Content> contentQuery = _resolver.query(xPathQuery); 854 AmetysObjectIterator<Content> contentIterator = contentQuery.iterator(); 855 if (contentIterator.hasNext()) 856 { 857 return contentIterator.next(); 858 } 859 860 return null; 861 } 862 863 /** 864 * Get the parameters map for user mybatis search 865 * @param orgUnitColumnKey the column name of the orgunit key 866 * @param logger the logger 867 * @return the parameter map 868 */ 869 protected Map<String, Object> _getSearchUserParameters(String orgUnitColumnKey, Logger logger) 870 { 871 Map<String, Object> params = new HashMap<>(); 872 params.put("loginColumnName", getLoginUserColumnName()); 873 params.put("tableUser", getUserTableName()); 874 params.put("tableOrgUnit", getTableName()); 875 params.put("joinColumnName", getOrgunitJoinColumnNameForUser()); 876 params.put("orgUnitColumnKey", orgUnitColumnKey); 877 params.put("orgUnitIdColumnName", getOrgUnitRemoteIdColumnName()); 878 879 String roleUserColumnName = getRoleUserColumnName(); 880 if (StringUtils.isNotBlank(roleUserColumnName)) 881 { 882 params.put("roleColumnName", roleUserColumnName); 883 } 884 885 return params; 886 } 887 888 /** 889 * Get the user id value 890 * @param userContent the user content 891 * @param logger the logger 892 * @return the user id value 893 */ 894 protected String _getUserIdValue(ModifiableDefaultContent userContent, Logger logger) 895 { 896 String loginMetadataName = getLoginUserMetadataName(); 897 return userContent.getMetadataHolder().getString(loginMetadataName, null); 898 } 899 900 @Override 901 protected void deleteUnexistingContents(Logger logger) 902 { 903 String query = _getContentPathQuery(null, null, null); 904 AmetysObjectIterable<ModifiableDefaultContent> contents = _resolver.query(query); 905 906 List<Content> contentsToRemove = _getContentsToRemove(contents); 907 if (!contentsToRemove.isEmpty()) 908 { 909 _nbDeletedContents += _deleteOrgUnitComponent.deleteContentsWithLog(contentsToRemove, MapUtils.EMPTY_SORTED_MAP, MapUtils.EMPTY_SORTED_MAP, logger); 910 } 911 } 912}