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.HashMap; 020import java.util.HashSet; 021import java.util.List; 022import java.util.Map; 023import java.util.Set; 024import java.util.stream.Collectors; 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.OutgoingReferences; 038import org.ametys.cms.content.references.OutgoingReferencesExtractor; 039import org.ametys.cms.repository.Content; 040import org.ametys.cms.repository.ContentQueryHelper; 041import org.ametys.cms.repository.ContentTypeExpression; 042import org.ametys.cms.repository.LanguageExpression; 043import org.ametys.cms.repository.ModifiableContent; 044import org.ametys.cms.repository.ModifiableDefaultContent; 045import org.ametys.cms.repository.WorkflowAwareContent; 046import org.ametys.cms.workflow.ContentWorkflowHelper; 047import org.ametys.core.observation.Event; 048import org.ametys.core.user.CurrentUserProvider; 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.UserDirectoryPageHandler; 063import org.ametys.plugins.userdirectory.expression.RemoteIdOrgunitExpression; 064 065import com.google.common.collect.Maps; 066import com.opensymphony.workflow.WorkflowException; 067 068/** 069 * Synchronizable collection for UD Orgunits 070 */ 071public class SQLSynchronizableUDOrgunitCollection extends SQLSynchronizableContentsCollection 072{ 073 /** The metadata name for orgUnit users repeater */ 074 public static final String ORGUNIT_USERS_METADATA = "users"; 075 /** The metadata name for orgUnit user in repeater */ 076 public static final String ORGUNIT_USER_METADATA = "user"; 077 /** The metadata name for orgUnit user role in repeater */ 078 public static final String ORGUNIT_USER_ROLE_METADATA = "role"; 079 080 /** The internal metadata name for orgUnit remote sql id */ 081 public static final String ORGUNIT_REMOTE_ID_INTERNAL_METADATA = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":orgunit-remote-id"; 082 083 private static final String __PARAM_SQL_TABLE_USER = "tableNameUser"; 084 private static final String __PARAM_SQL_ORGUNIT_JOIN_COLUMN_NAME = "orgUnitJoinColumnName"; 085 private static final String __PARAM_LOGIN_USER_METADATA_NAME = "loginUser"; 086 private static final String __PARAM_SQL_LOGIN_USER_COLUMN_NAME = "loginColumnName"; 087 private static final String __PARAM_SQL_ROLE_USER_COLUMN_NAME = "roleColumnName"; 088 private static final String __PARAM_SQL_ORGUNIT_REMOTE_ID_COLUMN_NAME = "orgunitRemoteIdColumnName"; 089 090 /** The map which link orgunit with this parent */ 091 protected Map<String, String> _orgUnitParents; 092 093 /** The map which link orgunit with users (userId and role) */ 094 protected Map<String, Map<String, String>> _orgUnitUsers; 095 096 /** The map which link orgunit with sql remote ids */ 097 protected Map<String, String> _orgUnitRemoteIds; 098 099 /** The set of orgunits to apply changes */ 100 protected Set<WorkflowAwareContent> _orgUnitsToApplyChanges; 101 102 /** The sql user search DAO */ 103 protected SQLUserSearchDAO _sqlUserDAO; 104 105 /** The organisation chart page handler */ 106 protected OrganisationChartPageHandler _orgChartPageHandler; 107 108 /** The content workflow helper */ 109 protected ContentWorkflowHelper _contentWorkflowHelper; 110 111 /** The current user provider */ 112 protected CurrentUserProvider _userProvider; 113 114 /** The OutgoingReferences extractor */ 115 protected OutgoingReferencesExtractor _outgoingReferencesExtractor; 116 117 /** The delete orgUnit component */ 118 protected DeleteOrgUnitComponent _deleteOrgUnitComponent; 119 120 @Override 121 public void service(ServiceManager smanager) throws ServiceException 122 { 123 super.service(smanager); 124 _sqlUserDAO = (SQLUserSearchDAO) smanager.lookup(SQLUserSearchDAO.ROLE); 125 _orgChartPageHandler = (OrganisationChartPageHandler) smanager.lookup(OrganisationChartPageHandler.ROLE); 126 _contentWorkflowHelper = (ContentWorkflowHelper) smanager.lookup(ContentWorkflowHelper.ROLE); 127 _userProvider = (CurrentUserProvider) smanager.lookup(CurrentUserProvider.ROLE); 128 _outgoingReferencesExtractor = (OutgoingReferencesExtractor) smanager.lookup(OutgoingReferencesExtractor.ROLE); 129 _deleteOrgUnitComponent = (DeleteOrgUnitComponent) smanager.lookup(DeleteOrgUnitComponent.ROLE); 130 } 131 132 /** 133 * Get the name of user SQL table 134 * @return The user SQL table name 135 */ 136 public String getUserTableName() 137 { 138 return (String) getParameterValues().get(__PARAM_SQL_TABLE_USER); 139 } 140 141 /** 142 * Get the name of the orgunit join column name of the user table 143 * @return The name of the orgunit join column name 144 */ 145 public String getOrgunitJoinColumnNameForUser() 146 { 147 return (String) getParameterValues().get(__PARAM_SQL_ORGUNIT_JOIN_COLUMN_NAME); 148 } 149 150 /** 151 * Get the login user metadata name 152 * @return The login user metadata name 153 */ 154 public String getLoginUserMetadataName() 155 { 156 return (String) getParameterValues().get(__PARAM_LOGIN_USER_METADATA_NAME); 157 } 158 159 /** 160 * Get the login user column name 161 * @return The login user column name 162 */ 163 public String getLoginUserColumnName() 164 { 165 return (String) getParameterValues().get(__PARAM_SQL_LOGIN_USER_COLUMN_NAME); 166 } 167 168 /** 169 * Get the role user column name 170 * @return The role user column name 171 */ 172 public String getRoleUserColumnName() 173 { 174 return (String) getParameterValues().get(__PARAM_SQL_ROLE_USER_COLUMN_NAME); 175 } 176 177 /** 178 * Get the orgunit remote id column name 179 * @return The orgunit remote id column name 180 */ 181 public String getOrgUnitRemoteIdColumnName() 182 { 183 return (String) getParameterValues().get(__PARAM_SQL_ORGUNIT_REMOTE_ID_COLUMN_NAME); 184 } 185 186 @Override 187 protected Map<String, Object> _getSearchParameters(Map<String, Object> parameters, int offset, int limit, List<Object> sort, List<String> columns) 188 { 189 // Add the sql column name for the orgunit id. 190 // It's for setting the ametys-internal:orgunit-remote-id and retrieve easily the orgUnit content with this Id 191 String orgUnitIdColumnName = getOrgUnitRemoteIdColumnName(); 192 if (!columns.contains(orgUnitIdColumnName)) 193 { 194 columns.add(orgUnitIdColumnName); 195 } 196 197 return super._getSearchParameters(parameters, offset, limit, sort, columns); 198 } 199 200 @Override 201 protected Map<String, Map<String, Object>> internalSearch(Map<String, Object> parameters, int offset, int limit, List<Object> sort, Logger logger) 202 { 203 Map<String, Map<String, Object>> internalSearch = super.internalSearch(parameters, offset, limit, sort, logger); 204 205 // Fill _orgUnitRemoteIds and _orgUnitParents maps with search results 206 String orgUnitRemoteIdColumnName = getOrgUnitRemoteIdColumnName(); 207 for (String orgUnitIdValue : internalSearch.keySet()) 208 { 209 Map<String, Object> orgUnitValues = internalSearch.get(orgUnitIdValue); 210 _orgUnitRemoteIds.put(orgUnitIdValue, orgUnitValues.get(orgUnitRemoteIdColumnName).toString()); 211 212 Map<String, List<String>> mapping = getMapping(); 213 if (mapping.containsKey(OrganisationChartPageHandler.METADATA_PARENT_ORGUNIT)) 214 { 215 // We take the first because it's no sense to defined two sql columns to define orgunit parents 216 String parentColumn = mapping.get(OrganisationChartPageHandler.METADATA_PARENT_ORGUNIT).get(0); 217 if (orgUnitValues.containsKey(parentColumn) && StringUtils.isNotBlank(orgUnitValues.get(parentColumn).toString())) 218 { 219 _orgUnitParents.put(orgUnitIdValue, orgUnitValues.get(parentColumn).toString()); 220 } 221 } 222 } 223 224 return internalSearch; 225 } 226 227 @Override 228 public List<ModifiableDefaultContent> _internalPopulate(Logger logger) 229 { 230 _orgUnitParents = new HashMap<>(); 231 _orgUnitRemoteIds = new HashMap<>(); 232 _orgUnitUsers = _getOrgUnitUser(logger); 233 _orgUnitsToApplyChanges = new HashSet<>(); 234 235 List<ModifiableDefaultContent> contents = super._internalPopulate(logger); 236 237 //All orgunits are imported, now we can set relation with parent orgunit 238 _setContentsRelationWithParentOrgunit(contents, logger); 239 240 for (WorkflowAwareContent content : _orgUnitsToApplyChanges) 241 { 242 try 243 { 244 _applyChanges(content); 245 } 246 catch (WorkflowException e) 247 { 248 logger.error("Can't apply change to content : " + content.getId()); 249 } 250 } 251 252 return contents; 253 } 254 255 @Override 256 protected List<ModifiableDefaultContent> _importOrSynchronizeContent(String idValue, String lang, Map<String, List<Object>> remoteValues, boolean forceImport, Logger logger) 257 { 258 remoteValues.remove(OrganisationChartPageHandler.METADATA_PARENT_ORGUNIT); 259 260 List<ModifiableDefaultContent> contents = super._importOrSynchronizeContent(idValue, lang, remoteValues, forceImport, logger); 261 for (ModifiableDefaultContent content : contents) 262 { 263 try 264 { 265 content.getNode().setProperty(ORGUNIT_REMOTE_ID_INTERNAL_METADATA, _orgUnitRemoteIds.get(idValue)); 266 } 267 catch (RepositoryException e) 268 { 269 _nbError++; 270 logger.error("An error occurred while importing or synchronizing content", e); 271 } 272 } 273 274 return contents; 275 } 276 277 @Override 278 protected boolean additionalSynchronizeOperations(ModifiableDefaultContent content, Map<String, List<Object>> remoteValues, Logger logger) 279 { 280 boolean hasChanges = super.additionalSynchronizeOperations(content, remoteValues, logger); 281 return _additionalOperations(content, remoteValues, logger) || hasChanges; 282 } 283 284 @Override 285 protected boolean additionalImportOperations(ModifiableDefaultContent content, Map<String, List<Object>> remoteValues, Map<String, Object> importParams, Logger logger) 286 { 287 boolean hasChanges = super.additionalImportOperations(content, remoteValues, importParams, logger); 288 return _additionalOperations(content, remoteValues, logger) || hasChanges; 289 } 290 291 /** 292 * Do additional operations 293 * @param orgUnit the orgUnit content 294 * @param remoteValues the remote values 295 * @param logger the logger 296 * @return true if changes 297 */ 298 protected boolean _additionalOperations(ModifiableDefaultContent orgUnit, Map<String, List<Object>> remoteValues, Logger logger) 299 { 300 boolean hasChanges = false; 301 302 String orgUnitIdValue = _getIdFieldValue(orgUnit); 303 hasChanges = _synchronizeUserRepeaterOperation(orgUnit, orgUnitIdValue, logger); 304 305 return hasChanges; 306 } 307 308 /** 309 * Get orgUnit user map 310 * @param logger the logger 311 * @return the orgUnit user map 312 */ 313 protected Map<String, Map<String, String>> _getOrgUnitUser(Logger logger) 314 { 315 Map<String, Map<String, String>> orgUnitUsers = new HashMap<>(); 316 317 Map<String, List<String>> mapping = getMapping(); 318 String idField = getIdField(); 319 List<String> remoteOrgUnitKeys = mapping.get(idField); 320 if (remoteOrgUnitKeys != null && remoteOrgUnitKeys.size() > 0) 321 { 322 String remoteOrgUnitKey = remoteOrgUnitKeys.get(0); 323 324 Map<String, Object> userParameters = _getSearchUserParameters(remoteOrgUnitKey, logger); 325 List<Map<String, Object>> searchUserList = _sqlUserDAO.searchUser(userParameters, getDataSourceId()); 326 327 String loginColumnName = getLoginUserColumnName(); 328 String roleColumnName = getRoleUserColumnName(); 329 330 List<String> userColumns = new ArrayList<>(); 331 userColumns.add(loginColumnName); 332 userColumns.add(remoteOrgUnitKey); 333 if (StringUtils.isNotBlank(roleColumnName)) 334 { 335 userColumns.add(roleColumnName); 336 } 337 338 for (Map<String, Object> userMap : searchUserList) 339 { 340 Map<String, Object> normalizedUserMap = _getNormalizedSearchResult(userColumns, userMap); 341 _fillOrgUnitUserMap(orgUnitUsers, remoteOrgUnitKey, loginColumnName, roleColumnName, normalizedUserMap, logger); 342 } 343 } 344 345 return orgUnitUsers; 346 } 347 348 /** 349 * Fill the orgUnitUsers map 350 * @param orgUnitUsers the orgunit user map 351 * @param remoteOrgUnitKey the remote key 352 * @param loginColumnName the login column name 353 * @param roleColumnName the role column name 354 * @param normalizedUserMap the normalized search user map 355 * @param logger the logger 356 */ 357 protected void _fillOrgUnitUserMap(Map<String, Map<String, String>> orgUnitUsers, String remoteOrgUnitKey, String loginColumnName, String roleColumnName, Map<String, Object> normalizedUserMap, Logger logger) 358 { 359 String loginValue = (normalizedUserMap.get(loginColumnName) == null) ? null : String.valueOf(normalizedUserMap.get(loginColumnName)); 360 String orgUnitIdValue = normalizedUserMap.get(remoteOrgUnitKey).toString(); 361 362 if (StringUtils.isNotBlank(loginValue)) 363 { 364 String roleValue = null; 365 if (StringUtils.isNotBlank(roleColumnName)) 366 { 367 roleValue = (normalizedUserMap.get(roleColumnName) == null) ? null : String.valueOf(normalizedUserMap.get(roleColumnName)); 368 } 369 370 if (!orgUnitUsers.containsKey(orgUnitIdValue)) 371 { 372 orgUnitUsers.put(orgUnitIdValue, Maps.newHashMap()); 373 } 374 375 Map<String, String> orgUnitUserMap = orgUnitUsers.get(orgUnitIdValue); 376 orgUnitUserMap.put(loginValue, roleValue); 377 } 378 else 379 { 380 logger.warn("Can't add user to orgunit '" + orgUnitIdValue + "' because the login value is blank ..."); 381 } 382 } 383 384 /** 385 * Set all orgunit parents relation for each synchronized content 386 * @param orgUnitContents the synchronized content 387 * @param logger the logger 388 */ 389 protected void _setContentsRelationWithParentOrgunit(List<ModifiableDefaultContent> orgUnitContents, Logger logger) 390 { 391 for (ModifiableDefaultContent orgUnitContent : orgUnitContents) 392 { 393 String orgUnitIdValue = _getIdFieldValue(orgUnitContent); 394 String parentIdSQL = _orgUnitParents.get(orgUnitIdValue); 395 396 Content parentContent = _orgChartPageHandler.getParentContent(orgUnitContent); 397 Content newParentContent = _getOrgUnitContentFromRemoteId(parentIdSQL, orgUnitContent.getLanguage(), logger); 398 399 if (StringUtils.isNotBlank(parentIdSQL) && newParentContent == null) 400 { 401 logger.warn("The orgUnit with sql id '{0}' doesn't exist. So this orgUnit is consider as null", parentIdSQL); 402 } 403 404 //We set the relation if parentContent and newParentContent is not the same 405 if (!(parentContent == null && newParentContent == null 406 || (parentContent != null && newParentContent != null && StringUtils.equals(parentContent.getId(), newParentContent.getId())))) 407 { 408 try 409 { 410 _setRelationWithParentOrgunit((ModifiableDefaultContent) newParentContent, orgUnitContent, logger); 411 } 412 catch (Exception e) 413 { 414 _nbError++; 415 String newParentId = newParentContent != null ? newParentContent.getId() : StringUtils.EMPTY; 416 logger.error("Can't set parent relation between the parent '" + newParentId + "' and the child '" + orgUnitContent.getId() + "'.", e); 417 } 418 } 419 } 420 } 421 422 /** 423 * Set the relation between the orgunit parent and its child 424 * @param parentContent the parent content 425 * @param childContent the child content 426 * @param logger the logger 427 * @throws WorkflowException if an error occurred 428 */ 429 protected void _setRelationWithParentOrgunit(ModifiableDefaultContent parentContent, ModifiableDefaultContent childContent, Logger logger) throws WorkflowException 430 { 431 ModifiableCompositeMetadata childMetadataHolder = childContent.getMetadataHolder(); 432 String parentOrgUnitId = childMetadataHolder.getString(OrganisationChartPageHandler.METADATA_PARENT_ORGUNIT, null); 433 434 if (StringUtils.isNotBlank(parentOrgUnitId)) 435 { 436 // We remove relation from old parent 437 ModifiableDefaultContent oldParentContent = _resolver.resolveById(parentOrgUnitId); 438 ModifiableCompositeMetadata oldParentMetadataHolder = oldParentContent.getMetadataHolder(); 439 440 String[] oldChildOrgUnit = oldParentMetadataHolder.getStringArray(OrganisationChartPageHandler.METADATA_CHILD_ORGUNIT, ArrayUtils.EMPTY_STRING_ARRAY); 441 442 String[] newValues = ArrayUtils.removeElement(oldChildOrgUnit, childContent.getId()); 443 ExternalizableMetadataHelper.setMetadata(oldParentMetadataHolder, OrganisationChartPageHandler.METADATA_CHILD_ORGUNIT, newValues); 444 445 _orgUnitsToApplyChanges.add(oldParentContent); 446 } 447 448 if (parentContent == null) 449 { 450 // The parent content is null, so just remove parent metadata 451 childContent.getMetadataHolder().removeMetadata(OrganisationChartPageHandler.METADATA_PARENT_ORGUNIT); 452 _orgUnitsToApplyChanges.add(childContent); 453 } 454 else 455 { 456 // Change child orgunit metadata for parent content 457 ModifiableCompositeMetadata parentMetadataHolder = parentContent.getMetadataHolder(); 458 String[] childOrgUnit = parentMetadataHolder.getStringArray(OrganisationChartPageHandler.METADATA_CHILD_ORGUNIT, ArrayUtils.EMPTY_STRING_ARRAY); 459 460 String[] newValues = ArrayUtils.add(childOrgUnit, childContent.getId()); 461 ExternalizableMetadataHelper.setMetadata(parentMetadataHolder, OrganisationChartPageHandler.METADATA_CHILD_ORGUNIT, newValues); 462 _orgUnitsToApplyChanges.add(parentContent); 463 464 // Change parent orgunit metadata for child content 465 childMetadataHolder.setMetadata(OrganisationChartPageHandler.METADATA_PARENT_ORGUNIT, parentContent.getId()); 466 _orgUnitsToApplyChanges.add(childContent); 467 } 468 } 469 470 private void _applyChanges(WorkflowAwareContent content) throws WorkflowException 471 { 472 Map<String, OutgoingReferences> outgoingReferencesByPath = _outgoingReferencesExtractor.getOutgoingReferences(content); 473 ((ModifiableContent) content).setOutgoingReferences(outgoingReferencesByPath); 474 475 content.saveChanges(); 476 ((DefaultAmetysObject) content).checkpoint(); 477 478 // Notify listeners 479 Map<String, Object> eventParams = new HashMap<>(); 480 eventParams.put(ObservationConstants.ARGS_CONTENT, content); 481 eventParams.put(ObservationConstants.ARGS_CONTENT_ID, content.getId()); 482 483 _observationManager.notify(new Event(ObservationConstants.EVENT_CONTENT_MODIFIED, _userProvider.getUser(), eventParams)); 484 485 _contentWorkflowHelper.doAction(content, 22); 486 } 487 488 /** 489 * Synchronize user repeater for orgunit content 490 * @param orgUnitContent the orgunit content 491 * @param orgUnitIdValue the orgUnit id value 492 * @param logger the logger 493 * @return true if changes 494 */ 495 protected boolean _synchronizeUserRepeaterOperation(ModifiableDefaultContent orgUnitContent, String orgUnitIdValue, Logger logger) 496 { 497 boolean hasChanges = false; 498 499 Map<String, String> orgUnitUsers = _orgUnitUsers.get(orgUnitIdValue); 500 if (orgUnitUsers != null) 501 { 502 hasChanges = _synchronizeUserRepeater(orgUnitContent, orgUnitUsers, logger); 503 } 504 505 return hasChanges; 506 } 507 508 /** 509 * True if the user repeater needs changes 510 * @param orgUnitContent the orgUnit content 511 * @param orgUnitUsers the map of orgunit users 512 * @param logger the logger 513 * @return true if the user repeater needs changes 514 */ 515 protected boolean _synchronizeUserRepeater(ModifiableDefaultContent orgUnitContent, Map<String, String> orgUnitUsers, Logger logger) 516 { 517 boolean hasChanges = false; 518 String roleUserColumnName = getRoleUserColumnName(); 519 520 ModifiableCompositeMetadata userRepeater = orgUnitContent.getMetadataHolder().getCompositeMetadata(ORGUNIT_USERS_METADATA, true); 521 522 int index = 1; 523 List<String> repeaterLogins = new ArrayList<>(); 524 String[] metadataNames = userRepeater.getMetadataNames(); 525 ArrayUtils.reverse(metadataNames); 526 for (String metadataName : metadataNames) 527 { 528 ModifiableCompositeMetadata entry = userRepeater.getCompositeMetadata(metadataName); 529 530 String userId = entry.getString(ORGUNIT_USER_METADATA, null); 531 String role = entry.getString(ORGUNIT_USER_ROLE_METADATA, null); 532 533 try 534 { 535 ModifiableDefaultContent content = _resolver.resolveById(userId); 536 String orgUnitIdValue = _getUserIdValue(content, logger); 537 if (!orgUnitUsers.containsKey(orgUnitIdValue)) 538 { 539 removeEntry(Integer.valueOf(metadataName), userRepeater); 540 hasChanges = true; 541 index--; 542 } 543 else if (StringUtils.isNotBlank(roleUserColumnName) && !StringUtils.equals(role, orgUnitUsers.get(orgUnitIdValue))) 544 { 545 String newRole = orgUnitUsers.get(orgUnitIdValue); 546 if (StringUtils.isNotBlank(newRole)) 547 { 548 entry.setMetadata(ORGUNIT_USER_ROLE_METADATA, newRole); 549 } 550 else 551 { 552 entry.removeMetadata(ORGUNIT_USER_ROLE_METADATA); 553 } 554 hasChanges = true; 555 } 556 repeaterLogins.add(orgUnitIdValue); 557 } 558 catch (UnknownAmetysObjectException e) 559 { 560 logger.warn("Can't find the content user in the repeater with id '" + userId + "'. So it will be deleted", e); 561 removeEntry(Integer.valueOf(metadataName), userRepeater); 562 hasChanges = true; 563 index--; 564 } 565 566 index++; 567 } 568 569 String lang = orgUnitContent.getLanguage(); 570 for (String login : orgUnitUsers.keySet()) 571 { 572 if (!repeaterLogins.contains(login)) 573 { 574 Content userContent = _getUserContent(login, lang, logger); 575 if (userContent != null) 576 { 577 ModifiableCompositeMetadata newEntry = userRepeater.getCompositeMetadata(String.valueOf(index), true); 578 newEntry.setMetadata(ORGUNIT_USER_METADATA, userContent.getId()); 579 580 String role = orgUnitUsers.get(login); 581 if (StringUtils.isNotBlank(role)) 582 { 583 newEntry.setMetadata(ORGUNIT_USER_ROLE_METADATA, role); 584 } 585 586 hasChanges = true; 587 index++; 588 } 589 else 590 { 591 logger.warn("Can't add user '" + login + "' to orgunit '" + orgUnitContent.getTitle() + "' because he doesn't exist"); 592 } 593 } 594 } 595 596 return hasChanges; 597 } 598 599 /** 600 * Removes the repeater entry at the given position. The position starts at index 1. 601 * The position can be an integer between 1 and the repeater size to remove an entry from the beginning 602 * 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) 603 * @param position The position of the entry to remove 604 * @param repositoryComposite the repository composite of the repeater 605 * @throws IllegalArgumentException if the position is not between the negative and positive repeater size 606 */ 607 protected void removeEntry(int position, ModifiableCompositeMetadata repositoryComposite) 608 { 609 int size = repositoryComposite.getMetadataNames().length; 610 if (1 <= position && position <= size) 611 { 612 // remove the entry 613 repositoryComposite.removeMetadata(String.valueOf(position)); 614 615 // rename all entries after the removed one 616 for (int currentEntryPosition = position + 1; currentEntryPosition <= size; currentEntryPosition++) 617 { 618 ModifiableCompositeMetadata entryRepositoryComposite = repositoryComposite.getCompositeMetadata(String.valueOf(currentEntryPosition)); 619 entryRepositoryComposite.rename(RepositoryConstants.NAMESPACE_PREFIX + ":" + String.valueOf(currentEntryPosition - 1)); 620 } 621 } 622 else if (-size < position && position <= 0) 623 { 624 // Find the positive equivalent position and call the removeEntry method with this position 625 removeEntry(size + position, repositoryComposite); 626 } 627 else 628 { 629 throw new IllegalArgumentException("The repeater has '" + size + "' entries. You can not remove an entry at position '" + position + "'."); 630 } 631 } 632 633 /** 634 * Get user content from login value 635 * @param loginValue the login value 636 * @param lang the language 637 * @param logger the logger 638 * @return the user content 639 */ 640 protected Content _getUserContent(String loginValue, String lang, Logger logger) 641 { 642 String loginMetadata = getLoginUserMetadataName(); 643 Set<String> contentTypes = _contentTypeEP.getSubTypes(UserDirectoryPageHandler.ABSTRACT_USER_CONTENT_TYPE); 644 645 Expression ctypeExpression = new ContentTypeExpression(Operator.EQ, contentTypes.toArray(new String[contentTypes.size()])); 646 LanguageExpression languageExpression = new LanguageExpression(Operator.EQ, lang); 647 StringExpression metadataExp = new StringExpression(loginMetadata, Operator.EQ, loginValue); 648 649 Expression userExp = new AndExpression(metadataExp, ctypeExpression, languageExpression); 650 String xPathQuery = ContentQueryHelper.getContentXPathQuery(userExp); 651 652 AmetysObjectIterable<Content> contentQuery = _resolver.query(xPathQuery); 653 AmetysObjectIterator<Content> contentIterator = contentQuery.iterator(); 654 if (contentIterator.hasNext()) 655 { 656 return contentIterator.next(); 657 } 658 659 return null; 660 } 661 662 /** 663 * Get orgunit content from the remote Id 664 * @param remoteId the remote Id 665 * @param lang the language 666 * @param logger the logger 667 * @return the orgunit content 668 */ 669 protected Content _getOrgUnitContentFromRemoteId(String remoteId, String lang, Logger logger) 670 { 671 Expression ctypeExpression = new ContentTypeExpression(Operator.EQ, OrganisationChartPageHandler.ORGUNIT_CONTENT_TYPE); 672 RemoteIdOrgunitExpression remoteIdOrgunitExpression = new RemoteIdOrgunitExpression(remoteId); 673 LanguageExpression languageExpression = new LanguageExpression(Operator.EQ, lang); 674 675 Expression userExp = new AndExpression(remoteIdOrgunitExpression, ctypeExpression, languageExpression); 676 String xPathQuery = ContentQueryHelper.getContentXPathQuery(userExp); 677 678 AmetysObjectIterable<Content> contentQuery = _resolver.query(xPathQuery); 679 AmetysObjectIterator<Content> contentIterator = contentQuery.iterator(); 680 if (contentIterator.hasNext()) 681 { 682 return contentIterator.next(); 683 } 684 685 return null; 686 } 687 688 /** 689 * Get the parameters map for user mybatis search 690 * @param orgUnitColumnKey the column name of the orgunit key 691 * @param logger the logger 692 * @return the parameter map 693 */ 694 protected Map<String, Object> _getSearchUserParameters(String orgUnitColumnKey, Logger logger) 695 { 696 Map<String, Object> params = new HashMap<>(); 697 params.put("loginColumnName", getLoginUserColumnName()); 698 params.put("tableUser", getUserTableName()); 699 params.put("tableOrgUnit", getTableName()); 700 params.put("joinColumnName", getOrgunitJoinColumnNameForUser()); 701 params.put("orgUnitColumnKey", orgUnitColumnKey); 702 params.put("orgUnitIdColumnName", getOrgUnitRemoteIdColumnName()); 703 704 String roleUserColumnName = getRoleUserColumnName(); 705 if (StringUtils.isNotBlank(roleUserColumnName)) 706 { 707 params.put("roleColumnName", roleUserColumnName); 708 } 709 710 return params; 711 } 712 713 /** 714 * Get the user id value 715 * @param userContent the user content 716 * @param logger the logger 717 * @return the user id value 718 */ 719 protected String _getUserIdValue(ModifiableDefaultContent userContent, Logger logger) 720 { 721 String loginMetadataName = getLoginUserMetadataName(); 722 return userContent.getMetadataHolder().getString(loginMetadataName, null); 723 } 724 725 @Override 726 @SuppressWarnings("unchecked") 727 protected void deleteUnexistingContents(Logger logger) 728 { 729 String query = _getContentPathQuery(null, null, null); 730 AmetysObjectIterable<ModifiableDefaultContent> contents = _resolver.query(query); 731 732 List<Content> contentsToRemove = _getContentsToRemove(contents); 733 734 contentsToRemove.stream().forEach(content -> logger.info("The content '{}' ({}) does not exist anymore in remote source: it will be deleted if possible.", content.getTitle(), content.getId())); 735 736 List<String> contentIds = contentsToRemove.stream() 737 .map(Content::getId) 738 .collect(Collectors.toList()); 739 740 logger.info("Trying to delete contents. This can take a while..."); 741 Map<String, Object> deleteResults = _deleteOrgUnitComponent.deleteContents(contentIds, MapUtils.EMPTY_SORTED_MAP, MapUtils.EMPTY_SORTED_MAP); 742 logger.info("Contents deleting process ended."); 743 744 for (String contentId : contentIds) 745 { 746 Map<String, Object> result = (Map<String, Object>) deleteResults.get(contentId); 747 List<String> deletedContents = (List<String>) result.get("deleted-contents"); 748 _nbDeletedContents += deletedContents.size(); 749 750 List<Content> referencedContents = (List<Content>) result.get("referenced-contents"); 751 if (referencedContents.size() > 0) 752 { 753 logger.info("The following contents cannot be deleted because they are referenced: {}", referencedContents.stream().map(c -> c.getId()).collect(Collectors.toList())); 754 } 755 756 List<Content> lockedContents = (List<Content>) result.get("locked-contents"); 757 if (lockedContents.size() > 0) 758 { 759 logger.info("The following contents cannot be deleted because they are locked: {}", lockedContents.stream().map(c -> c.getId()).collect(Collectors.toList())); 760 } 761 762 List<Content> undeletedContents = (List<Content>) result.get("undeleted-contents"); 763 if (undeletedContents.size() > 0) 764 { 765 logger.info("{} contents were not deleted. See previous logs for more information.", undeletedContents.size()); 766 } 767 } 768 769 } 770}