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.Debug; 040import com.unboundid.util.NotMutable; 041import com.unboundid.util.StaticUtils; 042import com.unboundid.util.ThreadSafety; 043import com.unboundid.util.ThreadSafetyLevel; 044 045import static com.unboundid.ldap.sdk.unboundidds.controls.ControlMessages.*; 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 060 * Nokia/Alcatel-Lucent 8661 server products. These classes provide support 061 * for proprietary functionality or for external specifications that are not 062 * considered stable or mature enough to be guaranteed to work in an 063 * interoperable way with 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<>(3); 148 149 elements.add(new ASN1OctetString(getDN())); 150 151 final ArrayList<ASN1Element> attrElements = new ArrayList<>(20); 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<>(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 = new ArrayList<>(attrElements.length); 197 for (final ASN1Element e : attrElements) 198 { 199 attrs.add(Attribute.decode(ASN1Sequence.decodeAsSequence(e))); 200 } 201 202 final ArrayList<JoinedEntry> nestedJoinResults; 203 if (elements.length == 3) 204 { 205 final ASN1Element[] nestedElements = 206 ASN1Sequence.decodeAsSequence(elements[2]).elements(); 207 nestedJoinResults = new ArrayList<>(nestedElements.length); 208 for (final ASN1Element e : nestedElements) 209 { 210 nestedJoinResults.add(decode(e)); 211 } 212 } 213 else 214 { 215 nestedJoinResults = new ArrayList<>(0); 216 } 217 218 return new JoinedEntry(dn, attrs, nestedJoinResults); 219 } 220 catch (final Exception e) 221 { 222 Debug.debugException(e); 223 224 throw new LDAPException(ResultCode.DECODING_ERROR, 225 ERR_JOINED_ENTRY_CANNOT_DECODE.get( 226 StaticUtils.getExceptionMessage(e)), 227 e); 228 } 229 } 230 231 232 233 /** 234 * Retrieves the list of nested join results for this joined entry. 235 * 236 * @return The list of nested join results for this joined entry, or an 237 * empty list if there are none. 238 */ 239 public List<JoinedEntry> getNestedJoinResults() 240 { 241 return nestedJoinResults; 242 } 243 244 245 246 /** 247 * Appends a string representation of this joined entry to the provided 248 * buffer. 249 * 250 * @param buffer The buffer to which the information should be appended. 251 */ 252 @Override() 253 public void toString(final StringBuilder buffer) 254 { 255 buffer.append("JoinedEntry(dn='"); 256 buffer.append(getDN()); 257 buffer.append("', attributes={"); 258 259 final Iterator<Attribute> attrIterator = getAttributes().iterator(); 260 while (attrIterator.hasNext()) 261 { 262 attrIterator.next().toString(buffer); 263 if (attrIterator.hasNext()) 264 { 265 buffer.append(", "); 266 } 267 } 268 269 buffer.append("}, nestedJoinResults={"); 270 271 final Iterator<JoinedEntry> entryIterator = nestedJoinResults.iterator(); 272 while (entryIterator.hasNext()) 273 { 274 entryIterator.next().toString(buffer); 275 if (entryIterator.hasNext()) 276 { 277 buffer.append(", "); 278 } 279 } 280 281 buffer.append("})"); 282 } 283}