001/* 002 * (C) Copyright 2006-2016 Nuxeo SA (http://nuxeo.com/) and others. 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 * 016 * Contributors: 017 * Nuxeo - initial API and implementation 018 * 019 */ 020 021package org.nuxeo.ecm.directory.ldap; 022 023import java.util.ArrayList; 024import java.util.Arrays; 025import java.util.Collections; 026import java.util.List; 027import java.util.Set; 028import java.util.TreeSet; 029 030import javax.naming.CompositeName; 031import javax.naming.InvalidNameException; 032import javax.naming.Name; 033import javax.naming.NamingEnumeration; 034import javax.naming.NamingException; 035import javax.naming.directory.Attribute; 036import javax.naming.directory.Attributes; 037import javax.naming.directory.BasicAttribute; 038import javax.naming.directory.BasicAttributes; 039import javax.naming.directory.DirContext; 040import javax.naming.directory.SchemaViolationException; 041import javax.naming.directory.SearchControls; 042import javax.naming.directory.SearchResult; 043import javax.naming.ldap.LdapName; 044import javax.naming.ldap.Rdn; 045 046import org.apache.commons.logging.Log; 047import org.apache.commons.logging.LogFactory; 048import org.apache.commons.lang.StringUtils; 049import org.nuxeo.common.xmap.annotation.XNode; 050import org.nuxeo.common.xmap.annotation.XNodeList; 051import org.nuxeo.common.xmap.annotation.XObject; 052import org.nuxeo.ecm.core.api.DocumentModel; 053import org.nuxeo.ecm.core.api.PropertyException; 054import org.nuxeo.ecm.directory.AbstractReference; 055import org.nuxeo.ecm.directory.BaseSession; 056import org.nuxeo.ecm.directory.Directory; 057import org.nuxeo.ecm.directory.DirectoryEntryNotFoundException; 058import org.nuxeo.ecm.directory.DirectoryException; 059import org.nuxeo.ecm.directory.DirectoryFieldMapper; 060import org.nuxeo.ecm.directory.ReferenceDescriptor; 061import org.nuxeo.ecm.directory.Session; 062import org.nuxeo.ecm.directory.ldap.filter.FilterExpressionCorrector; 063import org.nuxeo.ecm.directory.ldap.filter.FilterExpressionCorrector.FilterJobs; 064 065import com.sun.jndi.ldap.LdapURL; 066 067/** 068 * Implementation of the directory Reference interface that leverage two common ways of storing relationships in LDAP 069 * directories: 070 * <ul> 071 * <li>the static attribute strategy where a multi-valued attribute store the exhaustive list of distinguished names of 072 * the refereed entries (eg. the uniqueMember attribute of the groupOfUniqueNames objectclass)</li> 073 * <li>the dynamic attribute strategy where a potentially multi-valued attribute stores a ldap urls intensively 074 * describing the refereed LDAP entries (eg. the memberURLs attribute of the groupOfURLs objectclass)</li> 075 * </ul> 076 * <p> 077 * Please note that both static and dynamic references are resolved in read mode whereas only the static attribute 078 * strategy is used when creating new references or when deleting existing ones (write / update mode). 079 * <p> 080 * Some design considerations behind the implementation of such reference can be found at: 081 * http://jira.nuxeo.org/browse/NXP-1506 082 * 083 * @author Olivier Grisel <ogrisel@nuxeo.com> 084 */ 085@XObject(value = "ldapReference") 086public class LDAPReference extends AbstractReference implements Cloneable { 087 088 private static final Log log = LogFactory.getLog(LDAPReference.class); 089 090 @XNodeList(value = "dynamicReference", type = LDAPDynamicReferenceDescriptor[].class, componentType = LDAPDynamicReferenceDescriptor.class) 091 private LDAPDynamicReferenceDescriptor[] dynamicReferences; 092 093 @XNode("@forceDnConsistencyCheck") 094 public boolean forceDnConsistencyCheck; 095 096 protected LDAPDirectoryDescriptor targetDirectoryDescriptor; 097 098 /** 099 * Resolve staticAttributeId as distinguished names (true by default) such as in the uniqueMember field of 100 * groupOfUniqueNames. Set to false to resolve as simple id (as in memberUID of posixGroup for instance). 101 */ 102 @XNode("@staticAttributeIdIsDn") 103 private boolean staticAttributeIdIsDn = true; 104 105 @XNode("@staticAttributeId") 106 protected String staticAttributeId; 107 108 @XNode("@dynamicAttributeId") 109 protected String dynamicAttributeId; 110 111 public LDAPReference() { 112 super(null, null); 113 } 114 115 public LDAPReference(ReferenceDescriptor referenceDescriptor) { 116 super(referenceDescriptor.getFieldName(), referenceDescriptor.getDirectory()); 117 } 118 119 @XNode("@field") 120 public void setFieldName(String fieldName) { 121 this.fieldName = fieldName; 122 } 123 124 public static final List<String> EMPTY_STRING_LIST = Collections.emptyList(); 125 126 private LDAPFilterMatcher getFilterMatcher() { 127 return new LDAPFilterMatcher(); 128 } 129 130 /** 131 * @return true if the reference should resolve statically refereed entries (identified by dn-valued attribute) 132 * @throws DirectoryException 133 */ 134 public boolean isStatic() throws DirectoryException { 135 return getStaticAttributeId() != null; 136 } 137 138 public String getStaticAttributeId() throws DirectoryException { 139 return getStaticAttributeId(null); 140 } 141 142 public String getStaticAttributeId(DirectoryFieldMapper sourceFM) throws DirectoryException { 143 if (staticAttributeId != null) { 144 // explicitly provided attributeId 145 return staticAttributeId; 146 } 147 148 // sourceFM can be passed to avoid infinite loop in LDAPDirectory init 149 if (sourceFM == null) { 150 sourceFM = ((LDAPDirectory) getSourceDirectory()).getFieldMapper(); 151 } 152 String backendFieldId = sourceFM.getBackendField(fieldName); 153 if (fieldName.equals(backendFieldId)) { 154 // no specific backendField found and no staticAttributeId found 155 // either, this reference should not be statically resolved 156 return null; 157 } else { 158 // BBB: the field mapper has been explicitly used to specify the 159 // staticAttributeId value as this was the case before the 160 // introduction of the staticAttributeId dynamicAttributeId duality 161 log.warn(String.format("implicit static attribute definition through fieldMapping is deprecated, " 162 + "please update your setup with " 163 + "<ldapReference field=\"%s\" directory=\"%s\" staticAttributeId=\"%s\">", fieldName, 164 sourceDirectoryName, backendFieldId)); 165 return backendFieldId; 166 } 167 } 168 169 public List<LDAPDynamicReferenceDescriptor> getDynamicAttributes() { 170 return Arrays.asList(dynamicReferences); 171 } 172 173 public String getDynamicAttributeId() { 174 return dynamicAttributeId; 175 } 176 177 /** 178 * @return true if the reference should resolve dynamically refereed entries (identified by a LDAP url-valued 179 * attribute) 180 */ 181 public boolean isDynamic() { 182 return dynamicAttributeId != null; 183 } 184 185 @Override 186 @XNode("@directory") 187 public void setTargetDirectoryName(String targetDirectoryName) { 188 this.targetDirectoryName = targetDirectoryName; 189 } 190 191 @Override 192 public Directory getSourceDirectory() throws DirectoryException { 193 194 Directory sourceDir = super.getSourceDirectory(); 195 if (sourceDir instanceof LDAPDirectory) { 196 return sourceDir; 197 } else { 198 throw new DirectoryException(sourceDirectoryName 199 + " is not a LDAPDirectory and thus cannot be used in a reference for " + fieldName); 200 } 201 } 202 203 @Override 204 public Directory getTargetDirectory() throws DirectoryException { 205 Directory targetDir = super.getTargetDirectory(); 206 if (targetDir instanceof LDAPDirectory) { 207 return targetDir; 208 } else { 209 throw new DirectoryException(targetDirectoryName 210 + " is not a LDAPDirectory and thus cannot be referenced as target by " + fieldName); 211 } 212 } 213 214 protected LDAPDirectory getTargetLDAPDirectory() throws DirectoryException { 215 return (LDAPDirectory) getTargetDirectory(); 216 } 217 218 protected LDAPDirectory getSourceLDAPDirectory() throws DirectoryException { 219 return (LDAPDirectory) getSourceDirectory(); 220 } 221 222 protected LDAPDirectoryDescriptor getTargetDirectoryDescriptor() throws DirectoryException { 223 if (targetDirectoryDescriptor == null) { 224 targetDirectoryDescriptor = getTargetLDAPDirectory().getDescriptor(); 225 } 226 return targetDirectoryDescriptor; 227 } 228 229 /** 230 * Store new links using the LDAP staticAttributeId strategy. 231 * 232 * @see org.nuxeo.ecm.directory.Reference#addLinks(String, List) 233 */ 234 @Override 235 public void addLinks(String sourceId, List<String> targetIds) throws DirectoryException { 236 237 if (targetIds.isEmpty()) { 238 // optim: nothing to do, return silently without further creating 239 // session instances 240 return; 241 } 242 243 LDAPDirectory ldapTargetDirectory = (LDAPDirectory) getTargetDirectory(); 244 LDAPDirectory ldapSourceDirectory = (LDAPDirectory) getSourceDirectory(); 245 String attributeId = getStaticAttributeId(); 246 if (attributeId == null) { 247 if (log.isTraceEnabled()) { 248 log.trace(String.format("trying to edit a non-static reference from %s in directory %s: ignoring", 249 sourceId, ldapSourceDirectory.getName())); 250 } 251 return; 252 } 253 try (LDAPSession targetSession = (LDAPSession) ldapTargetDirectory.getSession(); 254 LDAPSession sourceSession = (LDAPSession) ldapSourceDirectory.getSession()) { 255 // fetch the entry to be able to run the security policy 256 // implemented in an entry adaptor 257 DocumentModel sourceEntry = sourceSession.getEntry(sourceId, false); 258 if (sourceEntry == null) { 259 throw new DirectoryException(String.format("could not add links from unexisting %s in directory %s", 260 sourceId, ldapSourceDirectory.getName())); 261 } 262 if (!BaseSession.isReadOnlyEntry(sourceEntry)) { 263 SearchResult ldapEntry = sourceSession.getLdapEntry(sourceId); 264 265 String sourceDn = ldapEntry.getNameInNamespace(); 266 Attribute storedAttr = ldapEntry.getAttributes().get(attributeId); 267 String emptyRefMarker = ldapSourceDirectory.getDescriptor().getEmptyRefMarker(); 268 Attribute attrToAdd = new BasicAttribute(attributeId); 269 for (String targetId : targetIds) { 270 if (staticAttributeIdIsDn) { 271 // TODO optim: avoid LDAP search request when targetDn 272 // can be forged client side (rdnAttribute = idAttribute and scope is onelevel) 273 ldapEntry = targetSession.getLdapEntry(targetId); 274 if (ldapEntry == null) { 275 log.warn(String.format( 276 "entry '%s' in directory '%s' not found: could not add link from '%s' in directory '%s' for '%s'", 277 targetId, ldapTargetDirectory.getName(), sourceId, ldapSourceDirectory.getName(), 278 this)); 279 continue; 280 } 281 String dn = ldapEntry.getNameInNamespace(); 282 if (storedAttr == null || !storedAttr.contains(dn)) { 283 attrToAdd.add(dn); 284 } 285 } else { 286 if (storedAttr == null || !storedAttr.contains(targetId)) { 287 attrToAdd.add(targetId); 288 } 289 } 290 } 291 if (attrToAdd.size() > 0) { 292 try { 293 // do the LDAP request to store missing dns 294 Attributes attrsToAdd = new BasicAttributes(); 295 attrsToAdd.put(attrToAdd); 296 297 if (log.isDebugEnabled()) { 298 log.debug(String.format("LDAPReference.addLinks(%s, [%s]): LDAP modifyAttributes dn='%s' " 299 + "mod_op='ADD_ATTRIBUTE' attrs='%s' [%s]", sourceId, 300 StringUtils.join(targetIds, ", "), sourceDn, attrsToAdd, this)); 301 } 302 sourceSession.dirContext.modifyAttributes(sourceDn, DirContext.ADD_ATTRIBUTE, attrsToAdd); 303 304 // robustly clean any existing empty marker now that we are sure that the list in not empty 305 if (storedAttr.contains(emptyRefMarker)) { 306 Attributes cleanAttrs = new BasicAttributes(attributeId, emptyRefMarker); 307 308 if (log.isDebugEnabled()) { 309 log.debug(String.format( 310 "LDAPReference.addLinks(%s, [%s]): LDAP modifyAttributes dn='%s'" 311 + " mod_op='REMOVE_ATTRIBUTE' attrs='%s' [%s]", sourceId, 312 StringUtils.join(targetIds, ", "), sourceDn, cleanAttrs, this)); 313 } 314 sourceSession.dirContext.modifyAttributes(sourceDn, DirContext.REMOVE_ATTRIBUTE, cleanAttrs); 315 } 316 } catch (SchemaViolationException e) { 317 if (isDynamic()) { 318 // we are editing an entry that has no static part 319 log.warn(String.format("cannot update dynamic reference in field %s for source %s", 320 getFieldName(), sourceId)); 321 } else { 322 // this is a real schema configuration problem, 323 // wrap up the exception 324 throw new DirectoryException(e); 325 } 326 } 327 } 328 } 329 } catch (NamingException e) { 330 throw new DirectoryException("addLinks failed: " + e.getMessage(), e); 331 } 332 } 333 334 /** 335 * Store new links using the LDAP staticAttributeId strategy. 336 * 337 * @see org.nuxeo.ecm.directory.Reference#addLinks(List, String) 338 */ 339 @Override 340 public void addLinks(List<String> sourceIds, String targetId) throws DirectoryException { 341 String attributeId = getStaticAttributeId(); 342 if (attributeId == null && !sourceIds.isEmpty()) { 343 log.warn("trying to edit a non-static reference: ignoring"); 344 return; 345 } 346 LDAPDirectory ldapTargetDirectory = (LDAPDirectory) getTargetDirectory(); 347 LDAPDirectory ldapSourceDirectory = (LDAPDirectory) getSourceDirectory(); 348 349 String emptyRefMarker = ldapSourceDirectory.getDescriptor().getEmptyRefMarker(); 350 try (LDAPSession targetSession = (LDAPSession) ldapTargetDirectory.getSession(); 351 LDAPSession sourceSession = (LDAPSession) ldapSourceDirectory.getSession()) { 352 if (!sourceSession.isReadOnly()) { 353 // compute the target dn to add to all the matching source 354 // entries 355 SearchResult ldapEntry = targetSession.getLdapEntry(targetId); 356 if (ldapEntry == null) { 357 throw new DirectoryException(String.format("could not add links to unexisting %s in directory %s", 358 targetId, ldapTargetDirectory.getName())); 359 } 360 String targetAttributeValue; 361 if (staticAttributeIdIsDn) { 362 targetAttributeValue = ldapEntry.getNameInNamespace(); 363 } else { 364 targetAttributeValue = targetId; 365 } 366 367 for (String sourceId : sourceIds) { 368 // fetch the entry to be able to run the security policy 369 // implemented in an entry adaptor 370 DocumentModel sourceEntry = sourceSession.getEntry(sourceId, false); 371 if (sourceEntry == null) { 372 log.warn(String.format( 373 "entry %s in directory %s not found: could not add link to %s in directory %s", 374 sourceId, ldapSourceDirectory.getName(), targetId, ldapTargetDirectory.getName())); 375 continue; 376 } 377 if (BaseSession.isReadOnlyEntry(sourceEntry)) { 378 // skip this entry since it cannot be edited to add the 379 // reference to targetId 380 log.warn(String.format( 381 "entry %s in directory %s is readonly: could not add link to %s in directory %s", 382 sourceId, ldapSourceDirectory.getName(), targetId, ldapTargetDirectory.getName())); 383 continue; 384 } 385 ldapEntry = sourceSession.getLdapEntry(sourceId); 386 String sourceDn = ldapEntry.getNameInNamespace(); 387 Attribute storedAttr = ldapEntry.getAttributes().get(attributeId); 388 if (storedAttr.contains(targetAttributeValue)) { 389 // no need to readd 390 continue; 391 } 392 try { 393 // add the new dn 394 Attributes attrs = new BasicAttributes(attributeId, targetAttributeValue); 395 396 if (log.isDebugEnabled()) { 397 log.debug(String.format("LDAPReference.addLinks([%s], %s): LDAP modifyAttributes dn='%s'" 398 + " mod_op='ADD_ATTRIBUTE' attrs='%s' [%s]", StringUtils.join(sourceIds, ", "), 399 targetId, sourceDn, attrs, this)); 400 } 401 sourceSession.dirContext.modifyAttributes(sourceDn, DirContext.ADD_ATTRIBUTE, attrs); 402 403 // robustly clean any existing empty marker now that we 404 // are sure that the list in not empty 405 if (storedAttr.contains(emptyRefMarker)) { 406 Attributes cleanAttrs = new BasicAttributes(attributeId, emptyRefMarker); 407 if (log.isDebugEnabled()) { 408 log.debug(String.format("LDAPReference.addLinks(%s, %s): LDAP modifyAttributes dn='%s'" 409 + " mod_op='REMOVE_ATTRIBUTE' attrs='%s' [%s]", 410 StringUtils.join(sourceIds, ", "), targetId, sourceDn, cleanAttrs.toString(), 411 this)); 412 } 413 sourceSession.dirContext.modifyAttributes(sourceDn, DirContext.REMOVE_ATTRIBUTE, cleanAttrs); 414 } 415 } catch (SchemaViolationException e) { 416 if (isDynamic()) { 417 // we are editing an entry that has no static part 418 log.warn(String.format("cannot add dynamic reference in field %s for target %s", 419 getFieldName(), targetId)); 420 } else { 421 // this is a real schema configuration problem, 422 // wrap the exception 423 throw new DirectoryException(e); 424 } 425 } 426 } 427 } 428 } catch (NamingException e) { 429 throw new DirectoryException("addLinks failed: " + e.getMessage(), e); 430 } 431 } 432 433 /** 434 * Fetch both statically and dynamically defined references and merge the results. 435 * 436 * @see org.nuxeo.ecm.directory.Reference#getSourceIdsForTarget(String) 437 */ 438 @Override 439 public List<String> getSourceIdsForTarget(String targetId) throws DirectoryException { 440 441 // container to hold merged references 442 Set<String> sourceIds = new TreeSet<>(); 443 SearchResult targetLdapEntry = null; 444 String targetDn = null; 445 446 // fetch all attributes when dynamic groups are used 447 boolean fetchAllAttributes = isDynamic(); 448 449 // step #1: resolve static references 450 String staticAttributeId = getStaticAttributeId(); 451 if (staticAttributeId != null) { 452 // step #1.1: fetch the dn of the targetId entry in the target 453 // directory by the static dn valued strategy 454 LDAPDirectory targetDir = getTargetLDAPDirectory(); 455 456 if (staticAttributeIdIsDn) { 457 try (LDAPSession targetSession = (LDAPSession) targetDir.getSession()) { 458 targetLdapEntry = targetSession.getLdapEntry(targetId, fetchAllAttributes); 459 if (targetLdapEntry == null) { 460 String msg = String.format("Failed to perform inverse lookup on LDAPReference" 461 + " resolving field '%s' of '%s' to entries of '%s'" 462 + " using the static content of attribute '%s':" 463 + " entry '%s' cannot be found in '%s'", fieldName, sourceDirectory, 464 targetDirectoryName, staticAttributeId, targetId, targetDirectoryName); 465 throw new DirectoryEntryNotFoundException(msg); 466 } 467 targetDn = pseudoNormalizeDn(targetLdapEntry.getNameInNamespace()); 468 469 } catch (NamingException e) { 470 throw new DirectoryException("error fetching " + targetId + " from " + targetDirectoryName + ": " 471 + e.getMessage(), e); 472 } 473 } 474 475 // step #1.2: search for entries that reference that dn in the 476 // source directory and collect their ids 477 LDAPDirectory ldapSourceDirectory = getSourceLDAPDirectory(); 478 479 String filterExpr = String.format("(&(%s={0})%s)", staticAttributeId, ldapSourceDirectory.getBaseFilter()); 480 String[] filterArgs = new String[1]; 481 482 if (staticAttributeIdIsDn) { 483 filterArgs[0] = targetDn; 484 } else { 485 filterArgs[0] = targetId; 486 } 487 488 String searchBaseDn = ldapSourceDirectory.getDescriptor().getSearchBaseDn(); 489 SearchControls sctls = ldapSourceDirectory.getSearchControls(); 490 try (LDAPSession sourceSession = (LDAPSession) ldapSourceDirectory.getSession()) { 491 if (log.isDebugEnabled()) { 492 log.debug(String.format("LDAPReference.getSourceIdsForTarget(%s): LDAP search search base='%s'" 493 + " filter='%s' args='%s' scope='%s' [%s]", targetId, searchBaseDn, filterExpr, 494 StringUtils.join(filterArgs, ", "), sctls.getSearchScope(), this)); 495 } 496 NamingEnumeration<SearchResult> results = sourceSession.dirContext.search(searchBaseDn, filterExpr, 497 filterArgs, sctls); 498 499 try { 500 while (results.hasMore()) { 501 Attributes attributes = results.next().getAttributes(); 502 // NXP-2461: check that id field is filled 503 Attribute attr = attributes.get(sourceSession.idAttribute); 504 if (attr != null) { 505 Object value = attr.get(); 506 if (value != null) { 507 sourceIds.add(value.toString()); 508 } 509 } 510 } 511 } finally { 512 results.close(); 513 } 514 } catch (NamingException e) { 515 throw new DirectoryException("error during reference search for " + filterArgs[0], e); 516 } 517 } 518 // step #2: resolve dynamic references 519 String dynamicAttributeId = this.dynamicAttributeId; 520 if (dynamicAttributeId != null) { 521 522 LDAPDirectory ldapSourceDirectory = getSourceLDAPDirectory(); 523 LDAPDirectory ldapTargetDirectory = getTargetLDAPDirectory(); 524 String searchBaseDn = ldapSourceDirectory.getDescriptor().getSearchBaseDn(); 525 526 try (LDAPSession sourceSession = (LDAPSession) ldapSourceDirectory.getSession(); 527 LDAPSession targetSession = (LDAPSession) ldapTargetDirectory.getSession()) { 528 // step #2.1: fetch the target entry to apply the ldap url 529 // filters of the candidate sources on it 530 if (targetLdapEntry == null) { 531 // only fetch the entry if not already fetched by the 532 // static 533 // attributes references resolution 534 targetLdapEntry = targetSession.getLdapEntry(targetId, fetchAllAttributes); 535 } 536 if (targetLdapEntry == null) { 537 String msg = String.format("Failed to perform inverse lookup on LDAPReference" 538 + " resolving field '%s' of '%s' to entries of '%s'" 539 + " using the dynamic content of attribute '%s':" + " entry '%s' cannot be found in '%s'", 540 fieldName, ldapSourceDirectory, targetDirectoryName, dynamicAttributeId, targetId, 541 targetDirectoryName); 542 throw new DirectoryException(msg); 543 } 544 targetDn = pseudoNormalizeDn(targetLdapEntry.getNameInNamespace()); 545 Attributes targetAttributes = targetLdapEntry.getAttributes(); 546 547 // step #2.2: find the list of entries that hold candidate 548 // dynamic links in the source directory 549 SearchControls sctls = ldapSourceDirectory.getSearchControls(); 550 sctls.setReturningAttributes(new String[] { sourceSession.idAttribute, dynamicAttributeId }); 551 String filterExpr = String.format("%s=*", dynamicAttributeId); 552 553 if (log.isDebugEnabled()) { 554 log.debug(String.format("LDAPReference.getSourceIdsForTarget(%s): LDAP search search base='%s'" 555 + " filter='%s' scope='%s' [%s]", targetId, searchBaseDn, filterExpr, 556 sctls.getSearchScope(), this)); 557 } 558 NamingEnumeration<SearchResult> results = sourceSession.dirContext.search(searchBaseDn, filterExpr, 559 sctls); 560 try { 561 while (results.hasMore()) { 562 // step #2.3: for each sourceId and each ldapUrl test 563 // whether the current target entry matches the 564 // collected 565 // URL 566 Attributes sourceAttributes = results.next().getAttributes(); 567 568 NamingEnumeration<?> ldapUrls = sourceAttributes.get(dynamicAttributeId).getAll(); 569 try { 570 while (ldapUrls.hasMore()) { 571 LdapURL ldapUrl = new LdapURL(ldapUrls.next().toString()); 572 String candidateDN = pseudoNormalizeDn(ldapUrl.getDN()); 573 // check base URL 574 if (!targetDn.endsWith(candidateDN)) { 575 continue; 576 } 577 578 // check onelevel scope constraints 579 if ("onelevel".equals(ldapUrl.getScope())) { 580 int targetDnSize = new LdapName(targetDn).size(); 581 int urlDnSize = new LdapName(candidateDN).size(); 582 if (targetDnSize - urlDnSize > 1) { 583 // target is not a direct child of the 584 // DN of the 585 // LDAP URL 586 continue; 587 } 588 } 589 590 // check that the target entry matches the 591 // filter 592 if (getFilterMatcher().match(targetAttributes, ldapUrl.getFilter())) { 593 // the target match the source url, add it 594 // to the 595 // collected ids 596 sourceIds.add(sourceAttributes.get(sourceSession.idAttribute).get().toString()); 597 } 598 } 599 } finally { 600 ldapUrls.close(); 601 } 602 } 603 } finally { 604 results.close(); 605 } 606 } catch (NamingException e) { 607 throw new DirectoryException("error during reference search for " + targetId, e); 608 } 609 } 610 611 /* 612 * This kind of reference is not supported because Active Directory use filter expression not yet supported by 613 * LDAPFilterMatcher. See NXP-4562 614 */ 615 if (dynamicReferences != null && dynamicReferences.length > 0) { 616 log.error("This kind of reference is not supported."); 617 } 618 619 return new ArrayList<>(sourceIds); 620 } 621 622 /** 623 * Fetches both statically and dynamically defined references and merges the results. 624 * 625 * @see org.nuxeo.ecm.directory.Reference#getSourceIdsForTarget(String) 626 */ 627 @Override 628 // XXX: broken, use getLdapTargetIds for a proper implementation 629 @SuppressWarnings("unchecked") 630 public List<String> getTargetIdsForSource(String sourceId) throws DirectoryException { 631 String schemaName = getSourceDirectory().getSchema(); 632 try (Session session = getSourceDirectory().getSession()) { 633 try { 634 return (List<String>) session.getEntry(sourceId).getProperty(schemaName, fieldName); 635 } catch (PropertyException e) { 636 throw new DirectoryException(e); 637 } 638 } 639 } 640 641 /** 642 * Simple helper that replaces ", " by "," in the provided dn and returns the lower case version of the result for 643 * comparison purpose. 644 * 645 * @param dn the raw unnormalized dn 646 * @return lowercase version without whitespace after commas 647 * @throws InvalidNameException 648 */ 649 protected static String pseudoNormalizeDn(String dn) throws InvalidNameException { 650 LdapName ldapName = new LdapName(dn); 651 List<String> rdns = new ArrayList<>(); 652 for (Rdn rdn : ldapName.getRdns()) { 653 String value = rdn.getValue().toString().toLowerCase().replaceAll(",", "\\\\,"); 654 String rdnStr = rdn.getType().toLowerCase() + "=" + value; 655 rdns.add(0, rdnStr); 656 } 657 return StringUtils.join(rdns, ','); 658 } 659 660 /** 661 * Optimized method to spare a LDAP request when the caller is a LDAPSession object that has already fetched the 662 * LDAP Attribute instances. 663 * <p> 664 * This method should return the same results as the sister method: org.nuxeo 665 * .ecm.directory.Reference#getTargetIdsForSource(java.lang.String) 666 * 667 * @return target reference ids 668 * @throws DirectoryException 669 */ 670 public List<String> getLdapTargetIds(Attributes attributes) throws DirectoryException { 671 672 Set<String> targetIds = new TreeSet<>(); 673 674 LDAPDirectory ldapTargetDirectory = (LDAPDirectory) getTargetDirectory(); 675 LDAPDirectoryDescriptor targetDirconfig = getTargetDirectoryDescriptor(); 676 String emptyRefMarker = ldapTargetDirectory.getDescriptor().getEmptyRefMarker(); 677 try (LDAPSession targetSession = (LDAPSession) ldapTargetDirectory.getSession()) { 678 String baseDn = pseudoNormalizeDn(targetDirconfig.getSearchBaseDn()); 679 680 // step #1: fetch ids referenced by static attributes 681 String staticAttributeId = getStaticAttributeId(); 682 Attribute staticAttribute = null; 683 if (staticAttributeId != null) { 684 staticAttribute = attributes.get(staticAttributeId); 685 } 686 687 if (staticAttribute != null && !staticAttributeIdIsDn) { 688 NamingEnumeration<?> staticContent = staticAttribute.getAll(); 689 try { 690 while (staticContent.hasMore()) { 691 String value = staticContent.next().toString(); 692 if (!emptyRefMarker.equals(value)) { 693 targetIds.add(value); 694 } 695 } 696 } finally { 697 staticContent.close(); 698 } 699 } 700 701 if (staticAttribute != null && staticAttributeIdIsDn) { 702 NamingEnumeration<?> targetDns = staticAttribute.getAll(); 703 try { 704 while (targetDns.hasMore()) { 705 String targetDn = targetDns.next().toString(); 706 707 if (!pseudoNormalizeDn(targetDn).endsWith(baseDn)) { 708 // optim: avoid network connections when obvious 709 if (log.isTraceEnabled()) { 710 log.trace(String.format("ignoring: dn='%s' (does not match '%s') for '%s'", targetDn, 711 baseDn, this)); 712 } 713 continue; 714 } 715 // find the id of the referenced entry 716 String id = null; 717 718 if (targetSession.rdnMatchesIdField()) { 719 // optim: do not fetch the entry to get its true id 720 // but 721 // guess it by reading the targetDn 722 LdapName name = new LdapName(targetDn); 723 String rdn = name.get(name.size() - 1); 724 int pos = rdn.indexOf("="); 725 id = rdn.substring(pos + 1); 726 } else { 727 id = getIdForDn(targetSession, targetDn); 728 if (id == null) { 729 log.warn(String.format( 730 "ignoring target '%s' (missing attribute '%s') while resolving reference '%s'", 731 targetDn, targetSession.idAttribute, this)); 732 continue; 733 } 734 } 735 if (forceDnConsistencyCheck) { 736 // check that the referenced entry is actually part 737 // of 738 // the target directory (takes care of the filters 739 // and 740 // the scope) 741 // this check can be very expensive on large groups 742 // and thus not enabled by default 743 if (!targetSession.hasEntry(id)) { 744 if (log.isTraceEnabled()) { 745 log.trace(String.format( 746 "ignoring target '%s' when resolving '%s' (not part of target" 747 + " directory by forced DN consistency check)", targetDn, this)); 748 } 749 continue; 750 } 751 } 752 // NXP-2461: check that id field is filled 753 if (id != null) { 754 targetIds.add(id); 755 } 756 } 757 } finally { 758 targetDns.close(); 759 } 760 } 761 // step #2: fetched dynamically referenced ids 762 String dynamicAttributeId = this.dynamicAttributeId; 763 Attribute dynamicAttribute = null; 764 if (dynamicAttributeId != null) { 765 dynamicAttribute = attributes.get(dynamicAttributeId); 766 } 767 if (dynamicAttribute != null) { 768 NamingEnumeration<?> rawldapUrls = dynamicAttribute.getAll(); 769 try { 770 while (rawldapUrls.hasMore()) { 771 LdapURL ldapUrl = new LdapURL(rawldapUrls.next().toString()); 772 String linkDn = pseudoNormalizeDn(ldapUrl.getDN()); 773 String directoryDn = pseudoNormalizeDn(targetDirconfig.getSearchBaseDn()); 774 int scope = SearchControls.ONELEVEL_SCOPE; 775 String scopePart = ldapUrl.getScope(); 776 if (scopePart != null && scopePart.toLowerCase().startsWith("sub")) { 777 scope = SearchControls.SUBTREE_SCOPE; 778 } 779 if (!linkDn.endsWith(directoryDn) && !directoryDn.endsWith(linkDn)) { 780 // optim #1: if the dns do not match, abort 781 continue; 782 } else if (directoryDn.endsWith(linkDn) && linkDn.length() < directoryDn.length() 783 && scope == SearchControls.ONELEVEL_SCOPE) { 784 // optim #2: the link dn is pointing to elements 785 // that at 786 // upperlevel than directory elements 787 continue; 788 } else { 789 790 // Search for references elements 791 targetIds.addAll(getReferencedElements(attributes, directoryDn, linkDn, 792 ldapUrl.getFilter(), scope)); 793 794 } 795 } 796 } finally { 797 rawldapUrls.close(); 798 } 799 } 800 801 if (dynamicReferences != null && dynamicReferences.length > 0) { 802 803 // Only the first Dynamic Reference is used 804 LDAPDynamicReferenceDescriptor dynAtt = dynamicReferences[0]; 805 806 Attribute baseDnsAttribute = attributes.get(dynAtt.baseDN); 807 Attribute filterAttribute = attributes.get(dynAtt.filter); 808 809 if (baseDnsAttribute != null && filterAttribute != null) { 810 811 NamingEnumeration<?> baseDns = null; 812 NamingEnumeration<?> filters = null; 813 814 try { 815 // Get the BaseDN value from the descriptor 816 baseDns = baseDnsAttribute.getAll(); 817 String linkDnValue = baseDns.next().toString(); 818 baseDns.close(); 819 linkDnValue = pseudoNormalizeDn(linkDnValue); 820 821 // Get the filter value from the descriptor 822 filters = filterAttribute.getAll(); 823 String filterValue = filters.next().toString(); 824 filters.close(); 825 826 // Get the scope value from the descriptor 827 int scope = "subtree".equalsIgnoreCase(dynAtt.type) ? SearchControls.SUBTREE_SCOPE 828 : SearchControls.ONELEVEL_SCOPE; 829 830 String directoryDn = pseudoNormalizeDn(targetDirconfig.getSearchBaseDn()); 831 832 // if the dns match, and if the link dn is pointing to 833 // elements that at upperlevel than directory elements 834 if ((linkDnValue.endsWith(directoryDn) || directoryDn.endsWith(linkDnValue)) 835 && !(directoryDn.endsWith(linkDnValue) && linkDnValue.length() < directoryDn.length() && scope == SearchControls.ONELEVEL_SCOPE)) { 836 837 // Correct the filter expression 838 filterValue = FilterExpressionCorrector.correctFilter(filterValue, FilterJobs.CORRECT_NOT); 839 840 // Search for references elements 841 targetIds.addAll(getReferencedElements(attributes, directoryDn, linkDnValue, filterValue, 842 scope)); 843 844 } 845 } finally { 846 if (baseDns != null) { 847 baseDns.close(); 848 } 849 850 if (filters != null) { 851 filters.close(); 852 } 853 } 854 855 } 856 857 } 858 // return merged attributes 859 return new ArrayList<String>(targetIds); 860 } catch (NamingException e) { 861 throw new DirectoryException("error computing LDAP references", e); 862 } 863 } 864 865 protected String getIdForDn(LDAPSession session, String dn) { 866 // the entry id is not based on the rdn, we thus need to 867 // fetch the LDAP entry to grab it 868 String[] attributeIdsToCollect = { session.idAttribute }; 869 Attributes entry; 870 try { 871 872 if (log.isDebugEnabled()) { 873 log.debug(String.format("LDAPReference.getIdForDn(session, %s): LDAP get dn='%s'" 874 + " attribute ids to collect='%s' [%s]", dn, dn, StringUtils.join(attributeIdsToCollect, ", "), 875 this)); 876 } 877 878 Name name = new CompositeName().add(dn); 879 entry = session.dirContext.getAttributes(name, attributeIdsToCollect); 880 } catch (NamingException e) { 881 return null; 882 } 883 // NXP-2461: check that id field is filled 884 Attribute attr = entry.get(session.idAttribute); 885 if (attr != null) { 886 try { 887 return attr.get().toString(); 888 } catch (NamingException e) { 889 } 890 } 891 return null; 892 } 893 894 /** 895 * Retrieve the elements referenced by the filter/BaseDN/Scope request. 896 * 897 * @param attributes Attributes of the referencer element 898 * @param directoryDn Dn of the Directory 899 * @param linkDn Dn specified in the parent 900 * @param filter Filter expression specified in the parent 901 * @param scope scope for the search 902 * @return The list of the referenced elements. 903 * @throws DirectoryException 904 * @throws NamingException 905 */ 906 private Set<String> getReferencedElements(Attributes attributes, String directoryDn, String linkDn, String filter, 907 int scope) throws DirectoryException, NamingException { 908 909 Set<String> targetIds = new TreeSet<>(); 910 911 LDAPDirectoryDescriptor targetDirconfig = getTargetDirectoryDescriptor(); 912 LDAPDirectory ldapTargetDirectory = (LDAPDirectory) getTargetDirectory(); 913 LDAPSession targetSession = (LDAPSession) ldapTargetDirectory.getSession(); 914 915 // use the most specific scope between the one specified in the 916 // Directory and the specified in the Parent 917 String dn = directoryDn.endsWith(linkDn) && directoryDn.length() > linkDn.length() ? directoryDn : linkDn; 918 919 // combine the ldapUrl search query with target 920 // directory own constraints 921 SearchControls scts = new SearchControls(); 922 923 // use the most specific scope 924 scts.setSearchScope(Math.min(scope, targetDirconfig.getSearchScope())); 925 926 // only fetch the ids of the targets 927 scts.setReturningAttributes(new String[] { targetSession.idAttribute }); 928 929 // combine the filter of the target directory with the 930 // provided filter if any 931 String targetFilter = targetDirconfig.getSearchFilter(); 932 if (filter == null || filter.length() == 0) { 933 filter = targetFilter; 934 } else if (targetFilter != null && targetFilter.length() > 0) { 935 filter = String.format("(&(%s)(%s))", targetFilter, filter); 936 } 937 938 // perform the request and collect the ids 939 if (log.isDebugEnabled()) { 940 log.debug(String.format("LDAPReference.getLdapTargetIds(%s): LDAP search dn='%s' " 941 + " filter='%s' scope='%s' [%s]", attributes, dn, dn, scts.getSearchScope(), this)); 942 } 943 944 Name name = new CompositeName().add(dn); 945 NamingEnumeration<SearchResult> results = targetSession.dirContext.search(name, filter, scts); 946 try { 947 while (results.hasMore()) { 948 // NXP-2461: check that id field is filled 949 Attribute attr = results.next().getAttributes().get(targetSession.idAttribute); 950 if (attr != null) { 951 String collectedId = attr.get().toString(); 952 if (collectedId != null) { 953 targetIds.add(collectedId); 954 } 955 } 956 957 } 958 } finally { 959 results.close(); 960 } 961 962 return targetIds; 963 } 964 965 /** 966 * Remove existing statically defined links for the given source id (dynamic references remain unaltered) 967 * 968 * @see org.nuxeo.ecm.directory.Reference#removeLinksForSource(String) 969 */ 970 @Override 971 public void removeLinksForSource(String sourceId) throws DirectoryException { 972 LDAPDirectory ldapTargetDirectory = (LDAPDirectory) getTargetDirectory(); 973 LDAPDirectory ldapSourceDirectory = (LDAPDirectory) getSourceDirectory(); 974 String attributeId = getStaticAttributeId(); 975 try (LDAPSession sourceSession = (LDAPSession) ldapSourceDirectory.getSession(); 976 LDAPSession targetSession = (LDAPSession) ldapTargetDirectory.getSession()) { 977 if (sourceSession.isReadOnly() || attributeId == null) { 978 // do not try to do anything on a read only server or to a 979 // purely dynamic reference 980 return; 981 } 982 // get the dn of the entry that matches sourceId 983 SearchResult sourceLdapEntry = sourceSession.getLdapEntry(sourceId); 984 if (sourceLdapEntry == null) { 985 throw new DirectoryException(String.format( 986 "cannot edit the links hold by missing entry '%s' in directory '%s'", sourceId, 987 ldapSourceDirectory.getName())); 988 } 989 String sourceDn = pseudoNormalizeDn(sourceLdapEntry.getNameInNamespace()); 990 991 Attribute oldAttr = sourceLdapEntry.getAttributes().get(attributeId); 992 if (oldAttr == null) { 993 // consider it as an empty attribute to simplify the following 994 // code 995 oldAttr = new BasicAttribute(attributeId); 996 } 997 Attribute attrToRemove = new BasicAttribute(attributeId); 998 999 NamingEnumeration<?> oldAttrs = oldAttr.getAll(); 1000 String targetBaseDn = pseudoNormalizeDn(ldapTargetDirectory.getDescriptor().getSearchBaseDn()); 1001 try { 1002 while (oldAttrs.hasMore()) { 1003 String targetKeyAttr = oldAttrs.next().toString(); 1004 1005 if (staticAttributeIdIsDn) { 1006 String dn = pseudoNormalizeDn(targetKeyAttr); 1007 if (forceDnConsistencyCheck) { 1008 String id = getIdForDn(targetSession, dn); 1009 if (id != null && targetSession.hasEntry(id)) { 1010 // this is an entry managed by the current 1011 // reference 1012 attrToRemove.add(dn); 1013 } 1014 } else if (dn.endsWith(targetBaseDn)) { 1015 // this is an entry managed by the current 1016 // reference 1017 attrToRemove.add(dn); 1018 } 1019 } else { 1020 attrToRemove.add(targetKeyAttr); 1021 } 1022 } 1023 } finally { 1024 oldAttrs.close(); 1025 } 1026 try { 1027 if (attrToRemove.size() == oldAttr.size()) { 1028 // use the empty ref marker to avoid empty attr 1029 String emptyRefMarker = ldapSourceDirectory.getDescriptor().getEmptyRefMarker(); 1030 Attributes emptyAttribute = new BasicAttributes(attributeId, emptyRefMarker); 1031 if (log.isDebugEnabled()) { 1032 log.debug(String.format( 1033 "LDAPReference.removeLinksForSource(%s): LDAP modifyAttributes key='%s' " 1034 + " mod_op='REPLACE_ATTRIBUTE' attrs='%s' [%s]", sourceId, sourceDn, 1035 emptyAttribute, this)); 1036 } 1037 sourceSession.dirContext.modifyAttributes(sourceDn, DirContext.REPLACE_ATTRIBUTE, emptyAttribute); 1038 } else if (attrToRemove.size() > 0) { 1039 // remove the attribute managed by the current reference 1040 Attributes attrsToRemove = new BasicAttributes(); 1041 attrsToRemove.put(attrToRemove); 1042 if (log.isDebugEnabled()) { 1043 log.debug(String.format( 1044 "LDAPReference.removeLinksForSource(%s): LDAP modifyAttributes dn='%s' " 1045 + " mod_op='REMOVE_ATTRIBUTE' attrs='%s' [%s]", sourceId, sourceDn, 1046 attrsToRemove, this)); 1047 } 1048 sourceSession.dirContext.modifyAttributes(sourceDn, DirContext.REMOVE_ATTRIBUTE, attrsToRemove); 1049 } 1050 } catch (SchemaViolationException e) { 1051 if (isDynamic()) { 1052 // we are editing an entry that has no static part 1053 log.warn(String.format("cannot remove dynamic reference in field %s for source %s", getFieldName(), 1054 sourceId)); 1055 } else { 1056 // this is a real schma configuration problem, wrapup the 1057 // exception 1058 throw new DirectoryException(e); 1059 } 1060 } 1061 } catch (NamingException e) { 1062 throw new DirectoryException("removeLinksForSource failed: " + e.getMessage(), e); 1063 } 1064 } 1065 1066 /** 1067 * Remove existing statically defined links for the given target id (dynamic references remain unaltered) 1068 * 1069 * @see org.nuxeo.ecm.directory.Reference#removeLinksForTarget(String) 1070 */ 1071 @Override 1072 public void removeLinksForTarget(String targetId) throws DirectoryException { 1073 if (!isStatic()) { 1074 // nothing to do: dynamic references cannot be updated 1075 return; 1076 } 1077 LDAPDirectory ldapTargetDirectory = (LDAPDirectory) getTargetDirectory(); 1078 LDAPDirectory ldapSourceDirectory = (LDAPDirectory) getSourceDirectory(); 1079 String attributeId = getStaticAttributeId(); 1080 try (LDAPSession targetSession = (LDAPSession) ldapTargetDirectory.getSession(); 1081 LDAPSession sourceSession = (LDAPSession) ldapSourceDirectory.getSession()) { 1082 if (!sourceSession.isReadOnly()) { 1083 // get the dn of the target that matches targetId 1084 String targetAttributeValue; 1085 1086 if (staticAttributeIdIsDn) { 1087 SearchResult targetLdapEntry = targetSession.getLdapEntry(targetId); 1088 if (targetLdapEntry == null) { 1089 String rdnAttribute = ldapTargetDirectory.getDescriptor().getRdnAttribute(); 1090 if (!rdnAttribute.equals(targetSession.idAttribute)) { 1091 log.warn(String.format( 1092 "cannot remove links to missing entry %s in directory %s for reference %s", 1093 targetId, ldapTargetDirectory.getName(), this)); 1094 return; 1095 } 1096 // the entry might have already been deleted, try to 1097 // re-forge it if possible (might not work if scope is 1098 // subtree) 1099 targetAttributeValue = String.format("%s=%s,%s", rdnAttribute, targetId, 1100 ldapTargetDirectory.getDescriptor().getSearchBaseDn()); 1101 } else { 1102 targetAttributeValue = pseudoNormalizeDn(targetLdapEntry.getNameInNamespace()); 1103 } 1104 } else { 1105 targetAttributeValue = targetId; 1106 } 1107 1108 // build a LDAP query to find entries that point to the target 1109 String searchFilter = String.format("(%s=%s)", attributeId, targetAttributeValue); 1110 String sourceFilter = ldapSourceDirectory.getBaseFilter(); 1111 1112 if (sourceFilter != null && !"".equals(sourceFilter)) { 1113 searchFilter = String.format("(&(%s)(%s))", searchFilter, sourceFilter); 1114 } 1115 1116 SearchControls scts = new SearchControls(); 1117 scts.setSearchScope(ldapSourceDirectory.getDescriptor().getSearchScope()); 1118 scts.setReturningAttributes(new String[] { attributeId }); 1119 1120 // find all source entries that point to the target key and 1121 // clean 1122 // those references 1123 if (log.isDebugEnabled()) { 1124 log.debug(String.format("LDAPReference.removeLinksForTarget(%s): LDAP search baseDn='%s' " 1125 + " filter='%s' scope='%s' [%s]", targetId, sourceSession.searchBaseDn, searchFilter, 1126 scts.getSearchScope(), this)); 1127 } 1128 NamingEnumeration<SearchResult> results = sourceSession.dirContext.search(sourceSession.searchBaseDn, 1129 searchFilter, scts); 1130 String emptyRefMarker = ldapSourceDirectory.getDescriptor().getEmptyRefMarker(); 1131 Attributes emptyAttribute = new BasicAttributes(attributeId, emptyRefMarker); 1132 1133 try { 1134 while (results.hasMore()) { 1135 SearchResult result = results.next(); 1136 Attributes attrs = result.getAttributes(); 1137 Attribute attr = attrs.get(attributeId); 1138 try { 1139 if (attr.size() == 1) { 1140 // the attribute holds the last reference, put 1141 // the 1142 // empty ref. marker before removing the 1143 // attribute 1144 // since empty attribute are often not allowed 1145 // by 1146 // the server schema 1147 if (log.isDebugEnabled()) { 1148 log.debug(String.format( 1149 "LDAPReference.removeLinksForTarget(%s): LDAP modifyAttributes key='%s' " 1150 + "mod_op='ADD_ATTRIBUTE' attrs='%s' [%s]", targetId, 1151 result.getNameInNamespace(), attrs, this)); 1152 } 1153 sourceSession.dirContext.modifyAttributes(result.getNameInNamespace(), 1154 DirContext.ADD_ATTRIBUTE, emptyAttribute); 1155 } 1156 // remove the reference to the target key 1157 attrs = new BasicAttributes(); 1158 attr = new BasicAttribute(attributeId); 1159 attr.add(targetAttributeValue); 1160 attrs.put(attr); 1161 if (log.isDebugEnabled()) { 1162 log.debug(String.format( 1163 "LDAPReference.removeLinksForTarget(%s): LDAP modifyAttributes key='%s' " 1164 + "mod_op='REMOVE_ATTRIBUTE' attrs='%s' [%s]", targetId, 1165 result.getNameInNamespace(), attrs, this)); 1166 } 1167 sourceSession.dirContext.modifyAttributes(result.getNameInNamespace(), 1168 DirContext.REMOVE_ATTRIBUTE, attrs); 1169 } catch (SchemaViolationException e) { 1170 if (isDynamic()) { 1171 // we are editing an entry that has no static 1172 // part 1173 log.warn(String.format("cannot remove dynamic reference in field %s for target %s", 1174 getFieldName(), targetId)); 1175 } else { 1176 // this is a real schema configuration problem, 1177 // wrapup the exception 1178 throw new DirectoryException(e); 1179 } 1180 } 1181 } 1182 } finally { 1183 results.close(); 1184 } 1185 } 1186 } catch (NamingException e) { 1187 throw new DirectoryException("removeLinksForTarget failed: " + e.getMessage(), e); 1188 } 1189 } 1190 1191 /** 1192 * Edit the list of statically defined references for a given target (dynamic references remain unaltered) 1193 * 1194 * @see org.nuxeo.ecm.directory.Reference#setSourceIdsForTarget(String, List) 1195 */ 1196 @Override 1197 public void setSourceIdsForTarget(String targetId, List<String> sourceIds) throws DirectoryException { 1198 removeLinksForTarget(targetId); 1199 addLinks(sourceIds, targetId); 1200 } 1201 1202 @Override 1203 public void setSourceIdsForTarget(String targetId, List<String> sourceIds, Session session) 1204 throws DirectoryException { 1205 setSourceIdsForTarget(targetId, sourceIds); 1206 } 1207 1208 /** 1209 * Set the list of statically defined references for a given source (dynamic references remain unaltered) 1210 * 1211 * @see org.nuxeo.ecm.directory.Reference#setTargetIdsForSource(String, List) 1212 */ 1213 @Override 1214 public void setTargetIdsForSource(String sourceId, List<String> targetIds) throws DirectoryException { 1215 removeLinksForSource(sourceId); 1216 addLinks(sourceId, targetIds); 1217 } 1218 1219 @Override 1220 public void setTargetIdsForSource(String sourceId, List<String> targetIds, Session session) 1221 throws DirectoryException { 1222 setTargetIdsForSource(sourceId, targetIds); 1223 } 1224 1225 @Override 1226 public void removeLinksForTarget(String targetId, Session session) throws DirectoryException { 1227 removeLinksForTarget(targetId); 1228 } 1229 1230 @Override 1231 public void removeLinksForSource(String sourceId, Session session) throws DirectoryException { 1232 removeLinksForSource(sourceId); 1233 } 1234 1235 @Override 1236 // to build helpful debug logs 1237 public String toString() { 1238 return String.format("LDAPReference to resolve field='%s' of sourceDirectory='%s'" 1239 + " with targetDirectory='%s'" + " and staticAttributeId='%s', dynamicAttributeId='%s'", fieldName, 1240 sourceDirectoryName, targetDirectoryName, staticAttributeId, dynamicAttributeId); 1241 } 1242 1243 /** 1244 * @since 5.6 1245 */ 1246 @Override 1247 public LDAPReference clone() { 1248 try { 1249 // basic fields are already copied by super.clone() 1250 return (LDAPReference) super.clone(); 1251 } catch (CloneNotSupportedException e) { 1252 throw new AssertionError(e); 1253 } 1254 } 1255 1256 @Override 1257 public void addLinks(String sourceId, List<String> targetIds, Session session) throws DirectoryException { 1258 // TODO to use for optimization 1259 addLinks(sourceId, targetIds); 1260 } 1261 1262 @Override 1263 public void addLinks(List<String> sourceIds, String targetId, Session session) throws DirectoryException { 1264 // TODO to use for optimization 1265 addLinks(sourceIds, targetId); 1266 } 1267 1268}