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