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