001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2008-2010 Sun Microsystems, Inc.
015 * Portions Copyright 2011-2016 ForgeRock AS.
016 */
017package org.opends.guitools.controlpanel.datamodel;
018
019import java.util.ArrayList;
020import java.util.Collections;
021import java.util.HashMap;
022import java.util.List;
023import java.util.Map;
024import java.util.SortedSet;
025import java.util.TreeSet;
026
027import javax.naming.CompositeName;
028import javax.naming.Name;
029import javax.naming.NamingEnumeration;
030import javax.naming.NamingException;
031import javax.naming.directory.Attribute;
032import javax.naming.directory.Attributes;
033import javax.naming.directory.SearchResult;
034
035import org.forgerock.opendj.ldap.AttributeDescription;
036import org.forgerock.opendj.ldap.ByteString;
037import org.forgerock.opendj.ldap.DN;
038import org.forgerock.opendj.ldap.schema.AttributeType;
039import org.opends.guitools.controlpanel.util.Utilities;
040import org.opends.server.core.DirectoryServer;
041import org.opends.server.types.AttributeBuilder;
042import org.opends.server.types.Entry;
043import org.forgerock.opendj.ldap.schema.ObjectClass;
044import org.opends.server.types.OpenDsException;
045import org.opends.server.util.LDIFReader;
046
047/**
048 * This is a commodity class used to wrap the SearchResult class of JNDI.
049 * Basically it retrieves all the attributes and values on the SearchResult and
050 * calculates its DN.  Using it we avoid having to handle the NamingException
051 * exceptions that most of the methods in SearchResult throw.
052 */
053public class CustomSearchResult implements Comparable<CustomSearchResult>
054{
055  private final String dn;
056  private Map<String, List<Object>> attributes;
057  private SortedSet<String> attrNames;
058  private String toString;
059  private int hashCode;
060
061  /**
062   * Constructor of an empty search result.  This constructor is used by the
063   * LDAP entry editor which 'build' their own CustomSearchResult.  The entry
064   * editors use some methods that require CustomSearchResult.
065   * @param dn the dn of the entry.
066   */
067  public CustomSearchResult(String dn)
068  {
069    this.dn = dn;
070    attributes = new HashMap<>();
071    attrNames = new TreeSet<>();
072    toString = calculateToString();
073    hashCode = calculateHashCode();
074  }
075
076  /**
077   * Constructor of a search result using a SearchResult as basis.
078   * @param sr the SearchResult.
079   * @param baseDN the base DN of the search that returned the SearchResult.
080   * @throws NamingException if there is an error retrieving the attribute
081   * values.
082   */
083  public CustomSearchResult(SearchResult sr, String baseDN)
084  throws NamingException
085  {
086    String sName = sr.getName();
087    Name name;
088    if (baseDN != null && baseDN.length() > 0)
089    {
090      if (sName != null && sName.length() > 0)
091      {
092        name = new CompositeName(sName);
093        name.add(baseDN);
094      }
095      else {
096        name = Utilities.getJNDIName(baseDN);
097      }
098    }
099    else {
100      name = new CompositeName(sName);
101    }
102    StringBuilder buf = new StringBuilder();
103    for (int i=0; i<name.size(); i++)
104    {
105      String n = name.get(i);
106      if (n != null && n.length() > 0)
107      {
108        if (buf.length() != 0)
109        {
110          buf.append(",");
111        }
112        buf.append(n);
113      }
114    }
115    dn = buf.toString();
116
117    attributes = new HashMap<>();
118    attrNames = new TreeSet<>();
119    Attributes attrs = sr.getAttributes();
120    if (attrs != null)
121    {
122      NamingEnumeration<?> en = attrs.getAll();
123      try
124      {
125        while (en.hasMore()) {
126          Attribute attr = (Attribute)en.next();
127          String attrName = attr.getID();
128          attrNames.add(attrName);
129          List<Object> values = new ArrayList<>();
130          for (int i=0; i<attr.size(); i++)
131          {
132            Object v = attr.get(i);
133            if (!"".equals(v.toString()))
134            {
135              values.add(v);
136            }
137          }
138          attributes.put(attrName.toLowerCase(), values);
139        }
140      }
141      finally
142      {
143        en.close();
144      }
145    }
146    toString = calculateToString();
147    hashCode = calculateHashCode();
148  }
149
150  /**
151   * Returns the DN of the entry.
152   * @return the DN of the entry.
153   */
154  public String getDN() {
155    return dn;
156  }
157
158  /**
159   * Returns the values for a given attribute.  It returns an empty Set if
160   * the attribute is not defined.
161   * @param name the name of the attribute.
162   * @return the values for a given attribute.  It returns an empty Set if
163   * the attribute is not defined.
164   */
165  public List<Object> getAttributeValues(String name) {
166    List<Object> values = attributes.get(name.toLowerCase());
167    if (values == null)
168    {
169      values = Collections.emptyList();
170    }
171    return values;
172  }
173
174  /**
175   * Returns all the attribute names of the entry.
176   * @return the attribute names of the entry.
177   */
178  public SortedSet<String> getAttributeNames() {
179    return attrNames;
180  }
181
182  @Override
183  public int compareTo(CustomSearchResult o) {
184    if (this.equals(o))
185    {
186      return 0;
187    }
188    return toString().compareTo(o.toString());
189  }
190
191  @Override
192  public boolean equals(Object o)
193  {
194    if (o == this)
195    {
196      return true;
197    }
198    if (o instanceof CustomSearchResult)
199    {
200      CustomSearchResult sr = (CustomSearchResult)o;
201      return getDN().equals(sr.getDN())
202          && getAttributeNames().equals(sr.getAttributeNames())
203          && attrValuesEqual(sr);
204    }
205    return false;
206  }
207
208  private boolean attrValuesEqual(CustomSearchResult sr)
209  {
210    for (String attrName : getAttributeNames())
211    {
212      if (!getAttributeValues(attrName).equals(sr.getAttributeValues(attrName)))
213      {
214        return false;
215      }
216    }
217    return true;
218  }
219
220  @Override
221  public String toString() {
222    return toString;
223  }
224
225  @Override
226  public int hashCode() {
227    return hashCode;
228  }
229
230  /**
231   * Sets the values for a given attribute name.
232   * @param attrName the name of the attribute.
233   * @param values the values for the attribute.
234   */
235  public void set(String attrName, List<Object> values)
236  {
237    attrNames.add(attrName);
238    attrName = attrName.toLowerCase();
239    attributes.put(attrName, values);
240    toString = calculateToString();
241    hashCode = calculateHashCode();
242  }
243
244  private String calculateToString()
245  {
246    return "dn: "+dn+"\nattributes: "+attributes;
247  }
248
249  private int calculateHashCode()
250  {
251    return 23 + toString.hashCode();
252  }
253
254  /**
255   * Gets the Entry object equivalent to this CustomSearchResult.
256   * The method assumes that the schema in DirectoryServer has been initialized.
257   * @return the Entry object equivalent to this CustomSearchResult.
258   * @throws OpenDsException if there is an error parsing the DN or retrieving
259   * the attributes definition and objectclasses in the schema of the server.
260   */
261  public Entry getEntry() throws OpenDsException
262  {
263    DN dn = DN.valueOf(getDN());
264    Map<ObjectClass,String> objectClasses = new HashMap<>();
265    Map<AttributeType,List<org.opends.server.types.Attribute>> userAttributes = new HashMap<>();
266    Map<AttributeType,List<org.opends.server.types.Attribute>> operationalAttributes = new HashMap<>();
267
268    for (String wholeName : getAttributeNames())
269    {
270      final AttributeDescription attrDesc = LDIFReader.parseAttrDescription(wholeName);
271      final AttributeType attrType = attrDesc.getAttributeType();
272
273      // See if this is an objectclass or an attribute.  Then get the
274      // corresponding definition and add the value to the appropriate hash.
275      if (attrType.isObjectClass())
276      {
277        for (Object value : getAttributeValues(attrType.getNameOrOID()))
278        {
279          String ocName = value.toString().trim();
280          objectClasses.put(DirectoryServer.getSchema().getObjectClass(ocName), ocName);
281        }
282      }
283      else
284      {
285        AttributeBuilder builder = new AttributeBuilder(attrDesc);
286        for (Object value : getAttributeValues(attrType.getNameOrOID()))
287        {
288          ByteString bs;
289          if (value instanceof byte[])
290          {
291            bs = ByteString.wrap((byte[])value);
292          }
293          else
294          {
295            bs = ByteString.valueOfUtf8(value.toString());
296          }
297          builder.add(bs);
298        }
299
300        List<org.opends.server.types.Attribute> attrList = builder.toAttributeList();
301        if (attrType.isOperational())
302        {
303          operationalAttributes.put(attrType, attrList);
304        }
305        else
306        {
307          userAttributes.put(attrType, attrList);
308        }
309      }
310    }
311
312    return new Entry(dn, objectClasses, userAttributes, operationalAttributes);
313  }
314}