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