001/* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt 010 * or http://forgerock.org/license/CDDLv1.0.html. 011 * See the License for the specific language governing permissions 012 * and limitations under the License. 013 * 014 * When distributing Covered Code, include this CDDL HEADER in each 015 * file and include the License file at legal-notices/CDDLv1_0.txt. 016 * If applicable, add the following below this CDDL HEADER, with the 017 * fields enclosed by brackets "[]" replaced with your own identifying 018 * information: 019 * Portions Copyright [yyyy] [name of copyright owner] 020 * 021 * CDDL HEADER END 022 * 023 * 024 * Copyright 2006-2009 Sun Microsystems, Inc. 025 * Portions Copyright 2014-2015 ForgeRock AS 026 */ 027package org.opends.server.protocols.ldap; 028 029import static org.opends.server.protocols.ldap.LDAPConstants.*; 030import static org.opends.server.util.CollectionUtils.*; 031import static org.opends.server.util.ServerConstants.*; 032import static org.opends.server.util.StaticUtils.*; 033 034import java.io.IOException; 035import java.util.ArrayList; 036import java.util.HashMap; 037import java.util.Iterator; 038import java.util.LinkedList; 039import java.util.List; 040import java.util.Map; 041 042import org.forgerock.opendj.io.ASN1Writer; 043import org.forgerock.opendj.ldap.ByteString; 044import org.opends.server.core.DirectoryServer; 045import org.opends.server.types.Attribute; 046import org.opends.server.types.AttributeBuilder; 047import org.opends.server.types.AttributeType; 048import org.opends.server.types.DN; 049import org.opends.server.types.Entry; 050import org.opends.server.types.LDAPException; 051import org.opends.server.types.ObjectClass; 052import org.opends.server.types.SearchResultEntry; 053import org.opends.server.util.Base64; 054 055/** 056 * This class defines the structures and methods for an LDAP search result entry 057 * protocol op, which is used to return entries that match the associated search 058 * criteria. 059 */ 060public class SearchResultEntryProtocolOp 061 extends ProtocolOp 062{ 063 /** The set of attributes for this search entry. */ 064 private LinkedList<LDAPAttribute> attributes; 065 066 /** The DN for this search entry. */ 067 private final DN dn; 068 069 /** The underlying search result entry. */ 070 private SearchResultEntry entry; 071 072 /** The LDAP version (determines how attribute options are handled). */ 073 private final int ldapVersion; 074 075 076 077 /** 078 * Creates a new LDAP search result entry protocol op with the specified DN 079 * and no attributes. 080 * 081 * @param dn The DN for this search result entry. 082 */ 083 public SearchResultEntryProtocolOp(DN dn) 084 { 085 this(dn, null, null, 3); 086 } 087 088 089 090 /** 091 * Creates a new LDAP search result entry protocol op with the specified DN 092 * and set of attributes. 093 * 094 * @param dn The DN for this search result entry. 095 * @param attributes The set of attributes for this search result entry. 096 */ 097 public SearchResultEntryProtocolOp(DN dn, 098 LinkedList<LDAPAttribute> attributes) 099 { 100 this(dn, attributes, null, 3); 101 } 102 103 104 105 /** 106 * Creates a new search result entry protocol op from the provided search 107 * result entry. 108 * 109 * @param searchEntry The search result entry object to use to create this 110 * search result entry protocol op. 111 */ 112 public SearchResultEntryProtocolOp(SearchResultEntry searchEntry) 113 { 114 this(searchEntry.getName(), null, searchEntry, 3); 115 } 116 117 118 119 /** 120 * Creates a new search result entry protocol op from the provided search 121 * result entry and ldap protocol version. 122 * 123 * @param searchEntry The search result entry object to use to create this 124 * search result entry protocol op. 125 * @param ldapVersion The version of the LDAP protocol. 126 */ 127 public SearchResultEntryProtocolOp(SearchResultEntry searchEntry, 128 int ldapVersion) 129 { 130 this(searchEntry.getName(), null, searchEntry, ldapVersion); 131 } 132 133 134 135 /** Generic constructor. */ 136 private SearchResultEntryProtocolOp(DN dn, 137 LinkedList<LDAPAttribute> attributes, SearchResultEntry searchEntry, 138 int ldapVersion) 139 { 140 this.dn = dn; 141 this.attributes = attributes; 142 this.entry = searchEntry; 143 this.ldapVersion = ldapVersion; 144 } 145 146 147 148 /** 149 * Retrieves the DN for this search result entry. 150 * 151 * @return The DN for this search result entry. 152 */ 153 public DN getDN() 154 { 155 return dn; 156 } 157 158 159 /** 160 * Retrieves the set of attributes for this search result entry. The returned 161 * list may be altered by the caller. 162 * 163 * @return The set of attributes for this search result entry. 164 */ 165 public LinkedList<LDAPAttribute> getAttributes() 166 { 167 LinkedList<LDAPAttribute> tmp = attributes; 168 if (tmp == null) 169 { 170 tmp = new LinkedList<>(); 171 if (entry != null) 172 { 173 if (ldapVersion == 2) 174 { 175 // Merge attributes having the same type into a single 176 // attribute. 177 boolean needsMerge; 178 Map<AttributeType, List<Attribute>> attrs = 179 entry.getUserAttributes(); 180 for (Map.Entry<AttributeType, List<Attribute>> attrList : attrs 181 .entrySet()) 182 { 183 needsMerge = true; 184 185 if (attrList != null && attrList.getValue().size() == 1) 186 { 187 Attribute a = attrList.getValue().get(0); 188 if (!a.hasOptions()) 189 { 190 needsMerge = false; 191 tmp.add(new LDAPAttribute(a)); 192 } 193 } 194 195 if (needsMerge) 196 { 197 AttributeBuilder builder = 198 new AttributeBuilder(attrList.getKey()); 199 for (Attribute a : attrList.getValue()) 200 { 201 builder.addAll(a); 202 } 203 tmp.add(new LDAPAttribute(builder.toAttribute())); 204 } 205 } 206 207 attrs = entry.getOperationalAttributes(); 208 for (Map.Entry<AttributeType, List<Attribute>> attrList : attrs 209 .entrySet()) 210 { 211 needsMerge = true; 212 213 if (attrList != null && attrList.getValue().size() == 1) 214 { 215 Attribute a = attrList.getValue().get(0); 216 if (!a.hasOptions()) 217 { 218 needsMerge = false; 219 tmp.add(new LDAPAttribute(a)); 220 } 221 } 222 223 if (needsMerge) 224 { 225 AttributeBuilder builder = 226 new AttributeBuilder(attrList.getKey()); 227 for (Attribute a : attrList.getValue()) 228 { 229 builder.addAll(a); 230 } 231 tmp.add(new LDAPAttribute(builder.toAttribute())); 232 } 233 } 234 } 235 else 236 { 237 // LDAPv3 238 for (List<Attribute> attrList : entry.getUserAttributes() 239 .values()) 240 { 241 for (Attribute a : attrList) 242 { 243 tmp.add(new LDAPAttribute(a)); 244 } 245 } 246 247 for (List<Attribute> attrList : entry 248 .getOperationalAttributes().values()) 249 { 250 for (Attribute a : attrList) 251 { 252 tmp.add(new LDAPAttribute(a)); 253 } 254 } 255 } 256 } 257 258 attributes = tmp; 259 260 // Since the attributes are mutable, null out the entry for consistency. 261 entry = null; 262 } 263 return attributes; 264 } 265 266 267 268 /** 269 * Retrieves the BER type for this protocol op. 270 * 271 * @return The BER type for this protocol op. 272 */ 273 @Override 274 public byte getType() 275 { 276 return OP_TYPE_SEARCH_RESULT_ENTRY; 277 } 278 279 280 281 /** 282 * Retrieves the name for this protocol op type. 283 * 284 * @return The name for this protocol op type. 285 */ 286 @Override 287 public String getProtocolOpName() 288 { 289 return "Search Result Entry"; 290 } 291 292 293 294 /** 295 * Writes this protocol op to an ASN.1 output stream. 296 * 297 * @param stream The ASN.1 output stream to write to. 298 * @throws IOException If a problem occurs while writing to the stream. 299 */ 300 @Override 301 public void write(ASN1Writer stream) throws IOException 302 { 303 stream.writeStartSequence(OP_TYPE_SEARCH_RESULT_ENTRY); 304 stream.writeOctetString(dn.toString()); 305 306 stream.writeStartSequence(); 307 SearchResultEntry tmp = entry; 308 if (ldapVersion == 3 && tmp != null) 309 { 310 for (List<Attribute> attrList : tmp.getUserAttributes() 311 .values()) 312 { 313 for (Attribute a : attrList) 314 { 315 writeAttribute(stream, a); 316 } 317 } 318 319 for (List<Attribute> attrList : tmp.getOperationalAttributes() 320 .values()) 321 { 322 for (Attribute a : attrList) 323 { 324 writeAttribute(stream, a); 325 } 326 } 327 } 328 else 329 { 330 for (LDAPAttribute attr : getAttributes()) 331 { 332 attr.write(stream); 333 } 334 } 335 stream.writeEndSequence(); 336 337 stream.writeEndSequence(); 338 } 339 340 341 342 /** 343 * Appends a string representation of this LDAP protocol op to the provided 344 * buffer. 345 * 346 * @param buffer The buffer to which the string should be appended. 347 */ 348 @Override 349 public void toString(StringBuilder buffer) 350 { 351 buffer.append("SearchResultEntry(dn="); 352 dn.toString(buffer); 353 buffer.append(", attrs={"); 354 355 LinkedList<LDAPAttribute> tmp = getAttributes(); 356 if (! tmp.isEmpty()) 357 { 358 Iterator<LDAPAttribute> iterator = tmp.iterator(); 359 iterator.next().toString(buffer); 360 361 while (iterator.hasNext()) 362 { 363 buffer.append(", "); 364 iterator.next().toString(buffer); 365 } 366 } 367 368 buffer.append("})"); 369 } 370 371 372 373 /** 374 * Appends a multi-line string representation of this LDAP protocol op to the 375 * provided buffer. 376 * 377 * @param buffer The buffer to which the information should be appended. 378 * @param indent The number of spaces from the margin that the lines should 379 * be indented. 380 */ 381 @Override 382 public void toString(StringBuilder buffer, int indent) 383 { 384 StringBuilder indentBuf = new StringBuilder(indent); 385 for (int i=0 ; i < indent; i++) 386 { 387 indentBuf.append(' '); 388 } 389 390 buffer.append(indentBuf); 391 buffer.append("Search Result Entry"); 392 buffer.append(EOL); 393 394 buffer.append(indentBuf); 395 buffer.append(" DN: "); 396 dn.toString(buffer); 397 buffer.append(EOL); 398 399 buffer.append(" Attributes:"); 400 buffer.append(EOL); 401 402 for (LDAPAttribute attribute : getAttributes()) 403 { 404 attribute.toString(buffer, indent+4); 405 } 406 } 407 408 409 410 /** 411 * Appends an LDIF representation of the entry to the provided buffer. 412 * 413 * @param buffer The buffer to which the entry should be appended. 414 * @param wrapColumn The column at which long lines should be wrapped. 415 */ 416 public void toLDIF(StringBuilder buffer, int wrapColumn) 417 { 418 // Add the DN to the buffer. 419 String dnString = dn.toString(); 420 int colsRemaining; 421 if (needsBase64Encoding(dnString)) 422 { 423 dnString = Base64.encode(getBytes(dnString)); 424 buffer.append("dn:: "); 425 426 colsRemaining = wrapColumn - 5; 427 } 428 else 429 { 430 buffer.append("dn: "); 431 432 colsRemaining = wrapColumn - 4; 433 } 434 435 int dnLength = dnString.length(); 436 if (dnLength <= colsRemaining || colsRemaining <= 0) 437 { 438 buffer.append(dnString); 439 buffer.append(EOL); 440 } 441 else 442 { 443 buffer.append(dnString, 0, colsRemaining); 444 buffer.append(EOL); 445 446 int startPos = colsRemaining; 447 while (dnLength - startPos > wrapColumn - 1) 448 { 449 buffer.append(" "); 450 buffer.append(dnString, startPos, startPos+wrapColumn-1); 451 buffer.append(EOL); 452 453 startPos += wrapColumn-1; 454 } 455 456 if (startPos < dnLength) 457 { 458 buffer.append(" "); 459 buffer.append(dnString.substring(startPos)); 460 buffer.append(EOL); 461 } 462 } 463 464 465 // Add the attributes to the buffer. 466 for (LDAPAttribute a : getAttributes()) 467 { 468 String name = a.getAttributeType(); 469 int nameLength = name.length(); 470 471 for (ByteString v : a.getValues()) 472 { 473 String valueString; 474 if (needsBase64Encoding(v)) 475 { 476 valueString = Base64.encode(v); 477 buffer.append(name); 478 buffer.append(":: "); 479 480 colsRemaining = wrapColumn - nameLength - 3; 481 } 482 else 483 { 484 valueString = v.toString(); 485 buffer.append(name); 486 buffer.append(": "); 487 488 colsRemaining = wrapColumn - nameLength - 2; 489 } 490 491 int valueLength = valueString.length(); 492 if (valueLength <= colsRemaining || colsRemaining <= 0) 493 { 494 buffer.append(valueString); 495 buffer.append(EOL); 496 } 497 else 498 { 499 buffer.append(valueString, 0, colsRemaining); 500 buffer.append(EOL); 501 502 int startPos = colsRemaining; 503 while (valueLength - startPos > wrapColumn - 1) 504 { 505 buffer.append(" "); 506 buffer.append(valueString, startPos, startPos+wrapColumn-1); 507 buffer.append(EOL); 508 509 startPos += wrapColumn-1; 510 } 511 512 if (startPos < valueLength) 513 { 514 buffer.append(" "); 515 buffer.append(valueString.substring(startPos)); 516 buffer.append(EOL); 517 } 518 } 519 } 520 } 521 522 523 // Make sure to add an extra blank line to ensure that there will be one 524 // between this entry and the next. 525 buffer.append(EOL); 526 } 527 528 529 530 /** 531 * Converts this protocol op to a search result entry. 532 * 533 * @return The search result entry created from this protocol op. 534 * 535 * @throws LDAPException If a problem occurs while trying to create the 536 * search result entry. 537 */ 538 public SearchResultEntry toSearchResultEntry() 539 throws LDAPException 540 { 541 if (entry != null) 542 { 543 return entry; 544 } 545 546 HashMap<ObjectClass,String> objectClasses = new HashMap<>(); 547 HashMap<AttributeType,List<Attribute>> userAttributes = new HashMap<>(); 548 HashMap<AttributeType,List<Attribute>> operationalAttributes = new HashMap<>(); 549 550 551 for (LDAPAttribute a : getAttributes()) 552 { 553 Attribute attr = a.toAttribute(); 554 AttributeType attrType = attr.getAttributeType(); 555 556 if (attrType.isObjectClass()) 557 { 558 for (ByteString os : a.getValues()) 559 { 560 String ocName = os.toString(); 561 ObjectClass oc = 562 DirectoryServer.getObjectClass(toLowerCase(ocName)); 563 if (oc == null) 564 { 565 oc = DirectoryServer.getDefaultObjectClass(ocName); 566 } 567 568 objectClasses.put(oc ,ocName); 569 } 570 } 571 else if (attrType.isOperational()) 572 { 573 List<Attribute> attrs = operationalAttributes.get(attrType); 574 if (attrs == null) 575 { 576 attrs = new ArrayList<>(1); 577 operationalAttributes.put(attrType, attrs); 578 } 579 attrs.add(attr); 580 } 581 else 582 { 583 List<Attribute> attrs = userAttributes.get(attrType); 584 if (attrs == null) 585 { 586 attrs = newArrayList(attr); 587 userAttributes.put(attrType, attrs); 588 } 589 else 590 { 591 // Check to see if any of the existing attributes in the list have the 592 // same set of options. If so, then add the values to that attribute. 593 boolean attributeSeen = false; 594 for (int i = 0; i < attrs.size(); i++) { 595 Attribute ea = attrs.get(i); 596 if (ea.optionsEqual(attr.getOptions())) 597 { 598 AttributeBuilder builder = new AttributeBuilder(ea); 599 builder.addAll(attr); 600 attrs.set(i, builder.toAttribute()); 601 attributeSeen = true; 602 } 603 } 604 if (!attributeSeen) 605 { 606 // This is the first occurrence of the attribute and options. 607 attrs.add(attr); 608 } 609 } 610 } 611 } 612 613 Entry entry = new Entry(dn, objectClasses, userAttributes, 614 operationalAttributes); 615 return new SearchResultEntry(entry); 616 } 617 618 619 620 /** Write an attribute without converting to an LDAPAttribute. */ 621 private void writeAttribute(ASN1Writer stream, Attribute a) 622 throws IOException 623 { 624 stream.writeStartSequence(); 625 stream.writeOctetString(a.getNameWithOptions()); 626 stream.writeStartSet(); 627 for (ByteString value : a) 628 { 629 stream.writeOctetString(value); 630 } 631 stream.writeEndSequence(); 632 stream.writeEndSequence(); 633 } 634} 635