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