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.DN; 036import org.forgerock.opendj.ldap.schema.AttributeType; 037import org.opends.server.core.DirectoryServer; 038import org.opends.server.types.Attribute; 039import org.opends.server.types.AttributeBuilder; 040import org.opends.server.types.Entry; 041import org.opends.server.types.LDAPException; 042import org.forgerock.opendj.ldap.schema.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.getAttributeDescription().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.getAttributeDescription().hasOptions()) 208 { 209 needsMerge = false; 210 tmp.add(new LDAPAttribute(a)); 211 } 212 } 213 214 if (needsMerge) 215 { 216 AttributeBuilder builder = new AttributeBuilder(attrList.getKey()); 217 for (Attribute a : attrList.getValue()) 218 { 219 builder.addAll(a); 220 } 221 tmp.add(new LDAPAttribute(builder.toAttribute())); 222 } 223 } 224 } 225 else 226 { 227 // LDAPv3 228 for (List<Attribute> attrList : entry.getUserAttributes().values()) 229 { 230 for (Attribute a : attrList) 231 { 232 tmp.add(new LDAPAttribute(a)); 233 } 234 } 235 236 for (List<Attribute> attrList : entry.getOperationalAttributes().values()) 237 { 238 for (Attribute a : attrList) 239 { 240 tmp.add(new LDAPAttribute(a)); 241 } 242 } 243 } 244 } 245 246 attributes = tmp; 247 248 // Since the attributes are mutable, null out the entry for consistency. 249 entry = null; 250 } 251 return attributes; 252 } 253 254 255 256 /** 257 * Retrieves the BER type for this protocol op. 258 * 259 * @return The BER type for this protocol op. 260 */ 261 @Override 262 public byte getType() 263 { 264 return OP_TYPE_SEARCH_RESULT_ENTRY; 265 } 266 267 268 269 /** 270 * Retrieves the name for this protocol op type. 271 * 272 * @return The name for this protocol op type. 273 */ 274 @Override 275 public String getProtocolOpName() 276 { 277 return "Search Result Entry"; 278 } 279 280 281 282 /** 283 * Writes this protocol op to an ASN.1 output stream. 284 * 285 * @param stream The ASN.1 output stream to write to. 286 * @throws IOException If a problem occurs while writing to the stream. 287 */ 288 @Override 289 public void write(ASN1Writer stream) throws IOException 290 { 291 stream.writeStartSequence(OP_TYPE_SEARCH_RESULT_ENTRY); 292 stream.writeOctetString(dn.toString()); 293 294 stream.writeStartSequence(); 295 SearchResultEntry tmp = entry; 296 if (ldapVersion == 3 && tmp != null) 297 { 298 for (List<Attribute> attrList : tmp.getUserAttributes() 299 .values()) 300 { 301 for (Attribute a : attrList) 302 { 303 writeAttribute(stream, a); 304 } 305 } 306 307 for (List<Attribute> attrList : tmp.getOperationalAttributes() 308 .values()) 309 { 310 for (Attribute a : attrList) 311 { 312 writeAttribute(stream, a); 313 } 314 } 315 } 316 else 317 { 318 for (LDAPAttribute attr : getAttributes()) 319 { 320 attr.write(stream); 321 } 322 } 323 stream.writeEndSequence(); 324 325 stream.writeEndSequence(); 326 } 327 328 329 330 /** 331 * Appends a string representation of this LDAP protocol op to the provided 332 * buffer. 333 * 334 * @param buffer The buffer to which the string should be appended. 335 */ 336 @Override 337 public void toString(StringBuilder buffer) 338 { 339 buffer.append("SearchResultEntry(dn="); 340 buffer.append(dn); 341 buffer.append(", attrs={"); 342 343 LinkedList<LDAPAttribute> tmp = getAttributes(); 344 if (! tmp.isEmpty()) 345 { 346 Iterator<LDAPAttribute> iterator = tmp.iterator(); 347 iterator.next().toString(buffer); 348 349 while (iterator.hasNext()) 350 { 351 buffer.append(", "); 352 iterator.next().toString(buffer); 353 } 354 } 355 356 buffer.append("})"); 357 } 358 359 360 361 /** 362 * Appends a multi-line string representation of this LDAP protocol op to the 363 * provided buffer. 364 * 365 * @param buffer The buffer to which the information should be appended. 366 * @param indent The number of spaces from the margin that the lines should 367 * be indented. 368 */ 369 @Override 370 public void toString(StringBuilder buffer, int indent) 371 { 372 StringBuilder indentBuf = new StringBuilder(indent); 373 for (int i=0 ; i < indent; i++) 374 { 375 indentBuf.append(' '); 376 } 377 378 buffer.append(indentBuf); 379 buffer.append("Search Result Entry"); 380 buffer.append(EOL); 381 382 buffer.append(indentBuf); 383 buffer.append(" DN: "); 384 buffer.append(dn); 385 buffer.append(EOL); 386 387 buffer.append(" Attributes:"); 388 buffer.append(EOL); 389 390 for (LDAPAttribute attribute : getAttributes()) 391 { 392 attribute.toString(buffer, indent+4); 393 } 394 } 395 396 397 398 /** 399 * Appends an LDIF representation of the entry to the provided buffer. 400 * 401 * @param buffer The buffer to which the entry should be appended. 402 * @param wrapColumn The column at which long lines should be wrapped. 403 */ 404 public void toLDIF(StringBuilder buffer, int wrapColumn) 405 { 406 // Add the DN to the buffer. 407 String dnString = dn.toString(); 408 int colsRemaining; 409 if (needsBase64Encoding(dnString)) 410 { 411 dnString = Base64.encode(getBytes(dnString)); 412 buffer.append("dn:: "); 413 414 colsRemaining = wrapColumn - 5; 415 } 416 else 417 { 418 buffer.append("dn: "); 419 420 colsRemaining = wrapColumn - 4; 421 } 422 423 int dnLength = dnString.length(); 424 if (dnLength <= colsRemaining || colsRemaining <= 0) 425 { 426 buffer.append(dnString); 427 buffer.append(EOL); 428 } 429 else 430 { 431 buffer.append(dnString, 0, colsRemaining); 432 buffer.append(EOL); 433 434 int startPos = colsRemaining; 435 while (dnLength - startPos > wrapColumn - 1) 436 { 437 buffer.append(" "); 438 buffer.append(dnString, startPos, startPos+wrapColumn-1); 439 buffer.append(EOL); 440 441 startPos += wrapColumn-1; 442 } 443 444 if (startPos < dnLength) 445 { 446 buffer.append(" "); 447 buffer.append(dnString.substring(startPos)); 448 buffer.append(EOL); 449 } 450 } 451 452 453 // Add the attributes to the buffer. 454 for (LDAPAttribute a : getAttributes()) 455 { 456 String name = a.getAttributeType(); 457 int nameLength = name.length(); 458 459 for (ByteString v : a.getValues()) 460 { 461 String valueString; 462 if (needsBase64Encoding(v)) 463 { 464 valueString = Base64.encode(v); 465 buffer.append(name); 466 buffer.append(":: "); 467 468 colsRemaining = wrapColumn - nameLength - 3; 469 } 470 else 471 { 472 valueString = v.toString(); 473 buffer.append(name); 474 buffer.append(": "); 475 476 colsRemaining = wrapColumn - nameLength - 2; 477 } 478 479 int valueLength = valueString.length(); 480 if (valueLength <= colsRemaining || colsRemaining <= 0) 481 { 482 buffer.append(valueString); 483 buffer.append(EOL); 484 } 485 else 486 { 487 buffer.append(valueString, 0, colsRemaining); 488 buffer.append(EOL); 489 490 int startPos = colsRemaining; 491 while (valueLength - startPos > wrapColumn - 1) 492 { 493 buffer.append(" "); 494 buffer.append(valueString, startPos, startPos+wrapColumn-1); 495 buffer.append(EOL); 496 497 startPos += wrapColumn-1; 498 } 499 500 if (startPos < valueLength) 501 { 502 buffer.append(" "); 503 buffer.append(valueString.substring(startPos)); 504 buffer.append(EOL); 505 } 506 } 507 } 508 } 509 510 511 // Make sure to add an extra blank line to ensure that there will be one 512 // between this entry and the next. 513 buffer.append(EOL); 514 } 515 516 517 518 /** 519 * Converts this protocol op to a search result entry. 520 * 521 * @return The search result entry created from this protocol op. 522 * 523 * @throws LDAPException If a problem occurs while trying to create the 524 * search result entry. 525 */ 526 public SearchResultEntry toSearchResultEntry() 527 throws LDAPException 528 { 529 if (entry != null) 530 { 531 return entry; 532 } 533 534 HashMap<ObjectClass,String> objectClasses = new HashMap<>(); 535 HashMap<AttributeType,List<Attribute>> userAttributes = new HashMap<>(); 536 HashMap<AttributeType,List<Attribute>> operationalAttributes = new HashMap<>(); 537 538 539 for (LDAPAttribute a : getAttributes()) 540 { 541 Attribute attr = a.toAttribute(); 542 AttributeDescription attrDesc = attr.getAttributeDescription(); 543 AttributeType attrType = attrDesc.getAttributeType(); 544 545 if (attrType.isObjectClass()) 546 { 547 for (ByteString os : a.getValues()) 548 { 549 String ocName = os.toString(); 550 ObjectClass oc = DirectoryServer.getSchema().getObjectClass(ocName); 551 objectClasses.put(oc, ocName); 552 } 553 } 554 else if (attrType.isOperational()) 555 { 556 List<Attribute> attrs = operationalAttributes.get(attrType); 557 if (attrs == null) 558 { 559 attrs = new ArrayList<>(1); 560 operationalAttributes.put(attrType, attrs); 561 } 562 attrs.add(attr); 563 } 564 else 565 { 566 List<Attribute> attrs = userAttributes.get(attrType); 567 if (attrs == null) 568 { 569 attrs = newArrayList(attr); 570 userAttributes.put(attrType, attrs); 571 } 572 else 573 { 574 // Check to see if any of the existing attributes in the list have the 575 // same set of options. If so, then add the values to that attribute. 576 boolean attributeSeen = false; 577 for (int i = 0; i < attrs.size(); i++) { 578 Attribute ea = attrs.get(i); 579 if (ea.getAttributeDescription().equals(attrDesc)) 580 { 581 AttributeBuilder builder = new AttributeBuilder(ea); 582 builder.addAll(attr); 583 attrs.set(i, builder.toAttribute()); 584 attributeSeen = true; 585 } 586 } 587 if (!attributeSeen) 588 { 589 // This is the first occurrence of the attribute and options. 590 attrs.add(attr); 591 } 592 } 593 } 594 } 595 596 Entry entry = new Entry(dn, objectClasses, userAttributes, 597 operationalAttributes); 598 return new SearchResultEntry(entry); 599 } 600 601 602 603 /** Write an attribute without converting to an LDAPAttribute. */ 604 private void writeAttribute(ASN1Writer stream, Attribute a) 605 throws IOException 606 { 607 stream.writeStartSequence(); 608 stream.writeOctetString(a.getAttributeDescription().toString()); 609 stream.writeStartSet(); 610 for (ByteString value : a) 611 { 612 stream.writeOctetString(value); 613 } 614 stream.writeEndSequence(); 615 stream.writeEndSequence(); 616 } 617} 618