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