001 /* 002 * Copyright 2009-2015 UnboundID Corp. 003 * All Rights Reserved. 004 */ 005 /* 006 * Copyright (C) 2015 UnboundID Corp. 007 * 008 * This program is free software; you can redistribute it and/or modify 009 * it under the terms of the GNU General Public License (GPLv2 only) 010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 011 * as published by the Free Software Foundation. 012 * 013 * This program is distributed in the hope that it will be useful, 014 * but WITHOUT ANY WARRANTY; without even the implied warranty of 015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 016 * GNU General Public License for more details. 017 * 018 * You should have received a copy of the GNU General Public License 019 * along with this program; if not, see <http://www.gnu.org/licenses>. 020 */ 021 package com.unboundid.ldap.sdk.unboundidds.controls; 022 023 024 025 import java.util.ArrayList; 026 import java.util.Collection; 027 import java.util.Collections; 028 import java.util.Iterator; 029 import java.util.List; 030 031 import com.unboundid.asn1.ASN1Element; 032 import com.unboundid.asn1.ASN1OctetString; 033 import com.unboundid.asn1.ASN1Sequence; 034 import com.unboundid.ldap.sdk.Attribute; 035 import com.unboundid.ldap.sdk.Entry; 036 import com.unboundid.ldap.sdk.LDAPException; 037 import com.unboundid.ldap.sdk.ReadOnlyEntry; 038 import com.unboundid.ldap.sdk.ResultCode; 039 import com.unboundid.util.NotMutable; 040 import com.unboundid.util.ThreadSafety; 041 import com.unboundid.util.ThreadSafetyLevel; 042 043 import static com.unboundid.ldap.sdk.unboundidds.controls.ControlMessages.*; 044 import static com.unboundid.util.Debug.*; 045 import static com.unboundid.util.StaticUtils.*; 046 047 048 049 /** 050 * <BLOCKQUOTE> 051 * <B>NOTE:</B> This class is part of the Commercial Edition of the UnboundID 052 * LDAP SDK for Java. It is not available for use in applications that 053 * include only the Standard Edition of the LDAP SDK, and is not supported for 054 * use in conjunction with non-UnboundID products. 055 * </BLOCKQUOTE> 056 * This class provides a joined entry, which is a read-only representation of an 057 * entry that has been joined with a search result entry using the LDAP join 058 * control. See the class-level documentation for the 059 * {@link JoinRequestControl} class for additional information and an example 060 * demonstrating its use. 061 * <BR><BR> 062 * Joined entries are encoded as follows: 063 * <PRE> 064 * JoinedEntry ::= SEQUENCE { 065 * objectName LDAPDN, 066 * attributes PartialAttributeList, 067 * nestedJoinResults SEQUENCE OF JoinedEntry OPTIONAL } 068 * </PRE> 069 */ 070 @NotMutable() 071 @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 072 public final class JoinedEntry 073 extends ReadOnlyEntry 074 { 075 /** 076 * The serial version UID for this serializable class. 077 */ 078 private static final long serialVersionUID = -6519864521813773703L; 079 080 081 082 // The list of nested join results for this joined entry. 083 private final List<JoinedEntry> nestedJoinResults; 084 085 086 087 /** 088 * Creates a new joined entry with the specified DN, attributes, and nested 089 * join results. 090 * 091 * @param entry The entry containing the DN and attributes to 092 * use for this joined entry. It must not be 093 * {@code null}. 094 * @param nestedJoinResults A list of nested join results for this joined 095 * entry. It may be {@code null} or empty if there 096 * are no nested join results. 097 */ 098 public JoinedEntry(final Entry entry, 099 final List<JoinedEntry> nestedJoinResults) 100 { 101 this(entry.getDN(), entry.getAttributes(), nestedJoinResults); 102 } 103 104 105 106 /** 107 * Creates a new joined entry with the specified DN, attributes, and nested 108 * join results. 109 * 110 * @param dn The DN for this joined entry. It must not be 111 * {@code null}. 112 * @param attributes The set of attributes for this joined entry. It 113 * must not be {@code null}. 114 * @param nestedJoinResults A list of nested join results for this joined 115 * entry. It may be {@code null} or empty if there 116 * are no nested join results. 117 */ 118 public JoinedEntry(final String dn, final Collection<Attribute> attributes, 119 final List<JoinedEntry> nestedJoinResults) 120 { 121 super(dn, attributes); 122 123 if (nestedJoinResults == null) 124 { 125 this.nestedJoinResults = Collections.emptyList(); 126 } 127 else 128 { 129 this.nestedJoinResults = Collections.unmodifiableList(nestedJoinResults); 130 } 131 } 132 133 134 135 /** 136 * Encodes this joined entry to an ASN.1 element. 137 * 138 * @return An ASN.1 element containing the encoded representation of this 139 * joined entry. 140 */ 141 ASN1Element encode() 142 { 143 final ArrayList<ASN1Element> elements = new ArrayList<ASN1Element>(3); 144 145 elements.add(new ASN1OctetString(getDN())); 146 147 final ArrayList<ASN1Element> attrElements = new ArrayList<ASN1Element>(); 148 for (final Attribute a : getAttributes()) 149 { 150 attrElements.add(a.encode()); 151 } 152 elements.add(new ASN1Sequence(attrElements)); 153 154 if (! nestedJoinResults.isEmpty()) 155 { 156 final ArrayList<ASN1Element> nestedElements = 157 new ArrayList<ASN1Element>(nestedJoinResults.size()); 158 for (final JoinedEntry je : nestedJoinResults) 159 { 160 nestedElements.add(je.encode()); 161 } 162 elements.add(new ASN1Sequence(nestedElements)); 163 } 164 165 return new ASN1Sequence(elements); 166 } 167 168 169 170 /** 171 * Decodes the provided ASN.1 element as a joined entry. 172 * 173 * @param element The ASN.1 element to decode as a joined entry. 174 * 175 * @return The decoded joined entry. 176 * 177 * @throws LDAPException If a problem occurs while attempting to decode the 178 * provided ASN.1 element as a joined entry. 179 */ 180 static JoinedEntry decode(final ASN1Element element) 181 throws LDAPException 182 { 183 try 184 { 185 final ASN1Element[] elements = 186 ASN1Sequence.decodeAsSequence(element).elements(); 187 final String dn = 188 ASN1OctetString.decodeAsOctetString(elements[0]).stringValue(); 189 190 final ASN1Element[] attrElements = 191 ASN1Sequence.decodeAsSequence(elements[1]).elements(); 192 final ArrayList<Attribute> attrs = 193 new ArrayList<Attribute>(attrElements.length); 194 for (final ASN1Element e : attrElements) 195 { 196 attrs.add(Attribute.decode(ASN1Sequence.decodeAsSequence(e))); 197 } 198 199 final ArrayList<JoinedEntry> nestedJoinResults; 200 if (elements.length == 3) 201 { 202 final ASN1Element[] nestedElements = 203 ASN1Sequence.decodeAsSequence(elements[2]).elements(); 204 nestedJoinResults = new ArrayList<JoinedEntry>(nestedElements.length); 205 for (final ASN1Element e : nestedElements) 206 { 207 nestedJoinResults.add(decode(e)); 208 } 209 } 210 else 211 { 212 nestedJoinResults = new ArrayList<JoinedEntry>(0); 213 } 214 215 return new JoinedEntry(dn, attrs, nestedJoinResults); 216 } 217 catch (Exception e) 218 { 219 debugException(e); 220 221 throw new LDAPException(ResultCode.DECODING_ERROR, 222 ERR_JOINED_ENTRY_CANNOT_DECODE.get(getExceptionMessage(e)), e); 223 } 224 } 225 226 227 228 /** 229 * Retrieves the list of nested join results for this joined entry. 230 * 231 * @return The list of nested join results for this joined entry, or an 232 * empty list if there are none. 233 */ 234 public List<JoinedEntry> getNestedJoinResults() 235 { 236 return nestedJoinResults; 237 } 238 239 240 241 /** 242 * Appends a string representation of this joined entry to the provided 243 * buffer. 244 * 245 * @param buffer The buffer to which the information should be appended. 246 */ 247 @Override() 248 public void toString(final StringBuilder buffer) 249 { 250 buffer.append("JoinedEntry(dn='"); 251 buffer.append(getDN()); 252 buffer.append("', attributes={"); 253 254 final Iterator<Attribute> attrIterator = getAttributes().iterator(); 255 while (attrIterator.hasNext()) 256 { 257 attrIterator.next().toString(buffer); 258 if (attrIterator.hasNext()) 259 { 260 buffer.append(", "); 261 } 262 } 263 264 buffer.append("}, nestedJoinResults={"); 265 266 final Iterator<JoinedEntry> entryIterator = nestedJoinResults.iterator(); 267 while (entryIterator.hasNext()) 268 { 269 entryIterator.next().toString(buffer); 270 if (entryIterator.hasNext()) 271 { 272 buffer.append(", "); 273 } 274 } 275 276 buffer.append("})"); 277 } 278 }