001/*
002 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003 *
004 * Copyright (c) 2005 Sun Microsystems Inc. All Rights Reserved
005 *
006 * The contents of this file are subject to the terms
007 * of the Common Development and Distribution License
008 * (the License). You may not use this file except in
009 * compliance with the License.
010 *
011 * You can obtain a copy of the License at
012 * https://opensso.dev.java.net/public/CDDLv1.0.html or
013 * opensso/legal/CDDLv1.0.txt
014 * See the License for the specific language governing
015 * permission and limitations under the License.
016 *
017 * When distributing Covered Code, include this CDDL
018 * Header Notice in each file and include the License file
019 * at opensso/legal/CDDLv1.0.txt.
020 * If applicable, add the following below the CDDL Header,
021 * with the fields enclosed by brackets [] replaced by
022 * your own identifying information:
023 * "Portions Copyrighted [year] [name of copyright owner]"
024 *
025 * $Id: PersistentObject.java,v 1.8 2009/07/02 20:27:01 hengming Exp $
026 *
027 * Portions Copyrighted 2011-2015 ForgeRock AS.
028 */
029
030package com.iplanet.ums;
031
032import com.iplanet.am.sdk.AMException;
033import com.iplanet.services.ldap.Attr;
034import com.iplanet.services.ldap.AttrSet;
035import com.iplanet.services.ldap.ModSet;
036import com.iplanet.services.ldap.aci.ACI;
037import com.iplanet.services.ldap.aci.ACIParseException;
038import com.iplanet.services.util.I18n;
039import com.iplanet.sso.SSOException;
040import com.iplanet.sso.SSOToken;
041import com.iplanet.sso.SSOTokenManager;
042import com.iplanet.ums.validation.Validation;
043import com.sun.identity.shared.debug.Debug;
044
045import java.io.Serializable;
046import java.security.Principal;
047import java.util.ArrayList;
048import java.util.Arrays;
049import java.util.Collection;
050import java.util.Collections;
051import java.util.HashSet;
052import java.util.Iterator;
053import java.util.Locale;
054
055import org.forgerock.openam.ldap.LDAPUtils;
056import org.forgerock.opendj.ldap.Attribute;
057import org.forgerock.opendj.ldap.ByteString;
058import org.forgerock.opendj.ldap.DN;
059import org.forgerock.opendj.ldap.Modification;
060import org.forgerock.opendj.ldap.ModificationType;
061import org.forgerock.opendj.ldap.RDN;
062import org.forgerock.opendj.ldap.SearchScope;
063
064/**
065 * Represents a persistent object in UMS. This is the base class for all objects
066 * that Object Management Module (OMM) manages in UMS.
067 *
068 * @supported.api
069 */
070public class PersistentObject implements ISearch, Serializable, IUMSConstants {
071
072    public static final String COMPUTED_MEMBER_ATTR_NAME = "nsRole";
073
074    private static Debug debug;
075    static {
076        debug = Debug.getInstance(IUMSConstants.UMS_DEBUG);
077    }
078
079    private static I18n i18n = I18n.getInstance(IUMSConstants.UMS_PKG);
080
081    /**
082     * Default Constructor
083     */
084    protected PersistentObject() {
085        super();
086    }
087
088    /**
089     * Constructor for PersistentObject given an authenticated session and guid,
090     * to instantiate from persistent storage.
091     * 
092     * @param session
093     *            Valid and authenticated session
094     * @param guid
095     *            Globally unique identifier for the entity
096     * @throws UMSException
097     *             for failure to find the object
098     * 
099     */
100    PersistentObject(Principal principal, Guid guid) throws UMSException {
101        String dn = guid.getDn();
102
103        if (principal == null || dn == null) {
104            String msg;
105            if (principal == null) {
106                msg = i18n.getString(IUMSConstants.BAD_PRINCIPAL_HDL);
107            } else {
108                msg = i18n.getString(IUMSConstants.BAD_GUID);
109            }
110
111            throw new IllegalArgumentException(msg);
112        }
113        setGuid(guid);
114        setPrincipal(principal);
115
116        // If reading in the object turns out to be too expensive, comment
117        // out the read. The read method will throw an exception for
118        // an unfound object due to access rights or bad guid.
119        // TODO: to be reviewed if we need to comment out this
120        //
121        read();
122    }
123
124    /**
125     * Constructor for in memory object to be added to the system. You can make
126     * the object persistent two ways:
127     * <P>
128     * 
129     * 1) call add on parent object (recommended)
130     * <P>
131     * 2) call save(...) method after all attributes for in memory object are
132     * set up properly.
133     * <P>
134     * 
135     * @param template
136     *            Object creation template. The template holds all the default
137     *            values such as objectclass and requirted attributes to be
138     *            supplied
139     * @param attrSet
140     *            Attribute set to construct the object in memory
141     * @throws UMSException
142     *             for failure to construct the object. The given attrSet needs
143     *             to provide the required attribute(s) defined in template
144     *
145     * @supported.api
146     */
147    public PersistentObject(CreationTemplate template, AttrSet attrSet)
148            throws UMSException {
149        m_attrSet = attrSet;
150        if (template == null) {
151            throw new UMSException(BAD_TEMPLATE);
152        }
153        m_namingAttribute = template.getNamingAttribute();
154        template.validateAttrSet(attrSet);
155        template.validateAttributes(attrSet);
156    }
157
158    /**
159     * Constructor for in memory object to be added to the system. You can make
160     * the object persistent two ways:
161     * <P>
162     * 
163     * 1) call add on parent object (recommended)
164     * <P>
165     * 2) call save(...) method after all attributes for in memory object are
166     * set up properly.
167     * <P>
168     * 
169     * @param template
170     *            Object creation template. The template holds all the default
171     *            values such as objectclass and requirted attributes to be
172     *            supplied
173     * @param attrSet
174     *            Attribute set to construct the object in memory
175     * @param namingAttribute
176     *            Internal naming attribute (ex: "ou").
177     * @throws UMSException
178     *             for failure to construct the object. The given attrSet needs
179     *             to provide the required attribute(s) defined in template
180     */
181    PersistentObject(CreationTemplate template, AttrSet attrSet,
182            String namingAttribute) throws UMSException {
183
184        m_attrSet = attrSet;
185        if (template == null) {
186            throw new UMSException(BAD_TEMPLATE);
187        }
188        template.validateAttrSet(attrSet);
189        template.validateAttributes(attrSet);
190        m_namingAttribute = namingAttribute;
191    }
192
193    /**
194     * Gets an attribute of the object. If the attribute is not in memory, the
195     * object is refreshed from persistent storage.
196     * 
197     * @param attrName
198     *            Name of the attribute to be queried
199     * @return Attribute value
200     *
201     * @supported.api
202     */
203    public Attr getAttribute(String attrName) {
204        Attr attr = getAttributeFromCache(attrName);
205        if ((attr == null) && isAttributeNotRead(attrName)
206                && (getGuid() != null) && (getPrincipal() != null)) {
207            try {
208                attr = readAttributeFromDataStore(attrName);
209            } catch (UMSException ex) {
210                if (debug.warningEnabled()) {
211                    debug.warning("PersistentObject.getAttribute: for DN: " +
212                        getGuid() + " attribute: " + attrName, ex);
213                }
214            }
215        }
216        return attr;
217    }
218
219    /**
220     * Gets an attribute value with a given locale
221     * 
222     * @param attrName
223     *            Name of the attribute
224     * @param locale
225     *            Locale of attribute to be retrieved
226     * @return Attribute value with the specified locale. May return null if the
227     *         attribute with locale not found. No fallback mechanism is
228     *         provided
229     * @see com.iplanet.ums.PersistentObject#getAttribute(String)
230     *
231     * @supported.api
232     */
233    public Attr getAttribute(String attrName, Locale locale)
234            throws UMSException {
235
236        if (locale == null) {
237            return getAttribute(attrName);
238        }
239
240        return getAttribute(Attr.getName(attrName, locale));
241    }
242
243    /**
244     * Gets attribute values
245     * 
246     * @param attrs
247     *            Array of strings representing attribute names
248     * @return attribute value set for the return values
249     * @see #getAttribute(String)
250     *
251     * @supported.api
252     */
253    public AttrSet getAttributes(String[] attrs) throws UMSException {
254        return getAttributes(attrs, false);
255    }
256
257    /**
258     * Gets attribute values
259     * 
260     * @param attrs
261     *            Array of strings representing attribute names
262     * @param cacheOnly
263     *            if true, read attributes from cache only without contacting
264     *            data stroe
265     * @return attribute value set for the return values
266     * @see #getAttribute(String)
267     *
268     * @supported.api
269     */
270    public AttrSet getAttributes(String[] attrs, boolean cacheOnly)
271            throws UMSException {
272        if (attrs == null) {
273            throw new IllegalArgumentException(i18n
274                    .getString(IUMSConstants.BAD_ATTRNAMES));
275        }
276        AttrSet attrSet = new AttrSet();
277        if (!cacheOnly) {
278            Collection attributesNotInCache = findAttributesNotRead(attrs);
279            if ((!attributesNotInCache.isEmpty()) && (getGuid() != null)
280                    && (getPrincipal() != null)) {
281                readAttributesFromDataStore(attributesNotInCache);
282            }
283        }
284        int length = attrs.length;
285        for (int i = 0; i < length; i++) {
286            Attr attr = getAttributeFromCache(attrs[i]);
287            if (attr != null) {
288                attrSet.add(attr);
289            }
290        }
291        return attrSet;
292    }
293
294    /**
295     * Returns attribute values with a specified locale.
296     * 
297     * @param attrNames Attribute names
298     * @param locale Locale of the attributes to be queried
299     * @return Attribute value set. May return null value for attribute(s) with
300     *         unfound locale. No fallback mechanism is provided.
301     * @see #getAttribute(String)
302     *
303     * @supported.api
304     */
305    public AttrSet getAttributes(String attrNames[], Locale locale)
306            throws UMSException {
307        if (locale == null)
308            return getAttributes(attrNames);
309
310        String[] namesWithLocale = new String[attrNames.length];
311
312        for (int i = 0; i < attrNames.length; i++) {
313            namesWithLocale[i] = Attr.getName(attrNames[i], locale);
314        }
315
316        return getAttributes(namesWithLocale);
317    }
318
319    /**
320     * Set an attribute value for the entity.
321     * <P>
322     * IMPORTANT: To make the changes persistent, you need to call the save
323     * method to save the changes.
324     * <P>
325     * 
326     * @param attr
327     *            Attribute and value
328     *
329     * @supported.api
330     */
331    public void setAttribute(Attr attr) {
332
333        if (attr == null || (attr.getName() == null)) {
334            throw new IllegalArgumentException(i18n
335                    .getString(IUMSConstants.ADD_NULL_OBJ));
336        }
337
338        checkCache();
339
340        if (m_attrSet == null)
341            m_attrSet = new AttrSet();
342
343        if (m_attrSet.contains(attr.getName())) {
344            modify(attr, ModificationType.REPLACE);
345        } else {
346            modify(attr, ModificationType.ADD);
347        }
348    }
349
350    /**
351     * Sets an attribute value with a given locale for the entity.
352     * <P>
353     * IMPORTANT: To make the changes persistent, you need to call the save
354     * method to save the changes.
355     * <P>
356     * 
357     * @param attr
358     *            Attribute and value
359     * @param locale
360     *            Intended locale of the attribute to be set
361     *
362     * @supported.api
363     */
364    public void setAttribute(Attr attr, Locale locale) {
365
366        if (locale == null) {
367            setAttribute(attr);
368            return;
369        }
370
371        // TODO: ??? should check if adding Attr.setName method makes more
372        // sense than recopying the data values of the passed in attribute
373        //
374        Attr attrWithLocale = new Attr(
375                Attr.getName(attr.getBaseName(), locale));
376        attrWithLocale.addValues(attr.getStringValues());
377        setAttribute(attrWithLocale);
378    }
379
380    /**
381     * Changes user password.
382     * 
383     * @param entryDN DN of the profile whose template is to be set
384     * @param attrName password attribute name
385     * @param oldPassword old password
386     * @param newPassword new password
387     * @throws AMException if an error occurs when changing user password
388     * @throws SSOException If user's single sign on token is invalid.
389     */
390    public void changePassword(String entryDN, String attrName,
391        String oldPassword, String newPassword) throws UMSException {
392
393        DataLayer.getInstance().changePassword(getGuid(), attrName,
394                oldPassword, newPassword);
395    }
396
397    /**
398     * Removes attribute value for the entity.
399     * <P>
400     * IMPORTANT: To make the changes persistent, you need to call the save
401     * method to save the changes.
402     * <P>
403     * 
404     * @param attr
405     *            Attribute to be removed
406     *
407     * @supported.api
408     */
409    public void removeAttribute(Attr attr) {
410        checkCache();
411        if (m_attrSet == null || m_attrSet.size() == 0) {
412            return;
413        }
414
415        modify(attr, ModificationType.DELETE);
416    }
417
418    /**
419     * Gets names for all available attributes for this entity
420     * 
421     * @return Array of strings representing attribute names
422     * 
423     * @supported.api
424     */
425    public String[] getAttributeNames() {
426        if (m_principal != null && m_guid != null && m_attrSet == null) {
427            try {
428                read();
429            } catch (UMSException e) {
430                // TODO log exception here.
431                if (debug.messageEnabled()) {
432                    debug.message("PersistentObject.getAttributeNames: " +
433                            "UMSException: " + e.getMessage());
434                }
435            }
436        }
437
438        if (m_attrSet != null) {
439            return m_attrSet.getAttributeNames();
440        } else {
441            return null;
442        }
443    }
444
445    /**
446     * Modifies attribute values for the entity.
447     * <P>
448     * IMPORTANT: To make the changes persistent, you need to call the save
449     * method to save the changes.
450     * <P>
451     * 
452     * @param modSet
453     *            Set of modification of attributes
454     * @see ModSet
455     *
456     * @supported.api
457     */
458    public void modify(Collection<Modification> modSet) {
459        checkCache();
460        if (m_modSet == null) {
461            m_modSet = new HashSet<>();
462        }
463        if (m_attrSet == null) {
464            m_attrSet = new AttrSet();
465        }
466
467        for (Modification mod : modSet) {
468            switch (mod.getModificationType().intValue()) {
469            case 0://ModSet.ADD:
470                m_attrSet.add(new Attr(mod.getAttribute()));
471                break;
472            case 1://ModSet.DELETE:
473                if (mod.getAttribute().size() == 0) {
474                    m_attrSet.remove(mod.getAttribute().getAttributeDescriptionAsString());
475                } else {
476                    Attribute attr = mod.getAttribute();
477                    for (ByteString value : attr) {
478                        m_attrSet.remove(attr.getAttributeDescriptionAsString(), value.toString());
479                    }
480                }
481                break;
482            case 2://ModSet.REPLACE:
483                m_attrSet.replace(new Attr(mod.getAttribute()));
484                break;
485            default:
486                break;
487            }
488
489            m_modSet.add(mod);
490
491        }
492    }
493
494    /**
495     * Modifies the values of a single attribute for the entity.
496     * <P>
497     * IMPORTANT: To make the changes persistent, you need to call the save
498     * method to save the changes.
499     * <P>
500     * 
501     * @param attr
502     *            Attribute value to be modified
503     * @param modificationType
504     *            Operation type in the modification. Input values include
505     * 
506     * <pre>
507     *               ModificationType.ADD,
508     *               ModificationType.DELETE,
509     *               ModificationType.REPLACE,
510     *               ModificationType.INCREMENT
511     * </pre>
512     * 
513     * @see ModSet
514     *
515     * @supported.api
516     */
517    public void modify(Attr attr, ModificationType modificationType) {
518        Modification modification = new Modification(modificationType, attr.toLDAPAttribute());
519        modify(Collections.singleton(modification));
520    }
521
522    /**
523     * Modify a single attribute for the entity.
524     * <P>
525     * IMPORTANT: To make the changes persistent, you need to call the save
526     * method to save the changes.
527     * <P>
528     * 
529     * @param attrName
530     *            Attribute name of the attribute to be modified
531     * @param value
532     *            String value of the attribute
533     * @param modificationType
534     *            Operation type in the modification. Input values include
535     * 
536     * <pre>
537     *                   ModificationType.ADD,
538     *                   ModificationType.DELETE,
539     *                   ModificationType.REPLACE,
540     *                   ModificationType.INCREMENT
541     * </pre>
542     * 
543     * @supported.api
544     */
545    public void modify(String attrName, String value, ModificationType modificationType) {
546        modify(new Attr(attrName, value), modificationType);
547    }
548
549    /**
550     * Get GUID of the given entity
551     * 
552     * @return the GUID.
553     *
554     * @supported.api
555     */
556    public Guid getGuid() {
557        return m_guid;
558    }
559
560    /**
561     * Renames the RDN to a new value. Note: The modified or added attribute
562     * values are not saved by this call.
563     * 
564     * @param newRDN
565     *            the new RDN value
566     * @param deleteOldName
567     *            if true old RDN value is deleted, otherwise the old value is
568     *            retained.
569     * 
570     * @throws AccessRightsException
571     *             if an access rights exception occurs.
572     * @throws EntryNotFoundException
573     *             if the entry is not found
574     * @throws UMSException
575     *             on failure to save to persistent storage
576     *
577     * @supported.api
578     */
579    public void rename(String newRDN, boolean deleteOldName)
580            throws AccessRightsException, EntryNotFoundException, UMSException {
581        String required = null;
582
583        if (m_principal == null) {
584            required = "principal";
585        } else if (m_guid == null) {
586            required = "guid";
587        }
588        if (required != null) {
589            // TODO: This is not an illegal argument case. Should be
590            // a more sophisticated exception.
591            String args[] = new String[1];
592
593            args[0] = required;
594            String msg = i18n.getString(IUMSConstants.NO_REQUIRED, args);
595            throw new UMSException(msg);
596        }
597
598        try {
599            DataLayer.getInstance().rename(getPrincipal(), getGuid(), newRDN,
600                    deleteOldName);
601        } finally {
602            // Must be set to new ID since the orignal DN would have changed now
603            RDN rdn = RDN.valueOf(newRDN);
604            DN parentDN = DN.valueOf(m_guid.toString()).parent();
605            parentDN.child(rdn);
606            m_guid.setDn(parentDN.toString());
607        }
608    }
609
610    /**
611     * Save the modification(s) to the object. Save the changes made so far for
612     * the persistent object. In other words, make the changes persistent for
613     * the object.
614     * <P>
615     * This save method takes no parameter. You use this save method when the
616     * object is already instantiated. For example,
617     * 
618     * <pre>
619     * User user = (User) UMSObject.getObject(principal, id);
620     * user.modify(&quot;telephonenumber&quot;, 
621     *      &quot;650.937.4444&quot;, ModificationType.REPLACE);
622     * user.save();
623     * </pre>
624     * 
625     * <P>
626     * 
627     * @throws AccessRightsException
628     *             if an access rights exception occurs.
629     * @throws EntryNotFoundException
630     *             if the entry is not found
631     * @throws UMSException
632     *             on failure to save to persistent storage
633     *
634     * @supported.api
635     */
636    public void save() throws AccessRightsException, EntryNotFoundException,
637            UMSException {
638        String required = null;
639        if (m_modSet == null) {
640            return;
641        }
642        if (m_principal == null) {
643            required = "principal";
644        } else if (m_guid == null) {
645            required = "guid";
646        }
647        if (required != null) {
648            // TODO: This is not an illegal argument case. Should be
649            // a more sophisticated exception.
650            String args[] = new String[1];
651
652            args[0] = required;
653            String msg = i18n.getString(IUMSConstants.NO_REQUIRED, args)
654                    + " - "
655                    + i18n.getString(IUMSConstants.OBJECT_NOT_PERSISTENT);
656            throw new UMSException(msg);
657        }
658
659        try {
660            DataLayer.getInstance().modify(getPrincipal(), getGuid(), m_modSet);
661        } finally {
662            // Remember to set this to null as the changes
663            // are made persistent after this call
664            m_modSet = null;
665        }
666    }
667
668    /**
669     * Gets the attribute name that specifies the ID (or RDN in terms of DN in
670     * ldap) component in an object. Subclasses may choose to override this
671     * function. For instance, User takes either "uid" or "cn" for its
672     * identification
673     * <P>
674     * 
675     * @return Attribute name for identification
676     *
677     * @supported.api
678     */
679    public String getNamingAttribute() {
680
681        if (m_guid == null) {
682            return m_namingAttribute;
683        }
684
685        DN dn = DN.valueOf(getDN());
686        if (dn.size() > 0) {
687            return LDAPUtils.rdnTypeFromDn(dn);
688        }
689        return null;
690    }
691
692    /**
693     * Gets the parent object
694     * 
695     * @return PersistentObject representing the parent object
696     * @throws UMSException
697     *             on failure instantiating the parent object
698     *
699     * @supported.api
700     */
701    public PersistentObject getParentObject() throws UMSException {
702
703        if (m_guid == null || m_principal == null) {
704            String msg;
705
706            if (m_principal == null) {
707                msg = i18n.getString(IUMSConstants.BAD_PRINCIPAL_HDL);
708            } else {
709                msg = i18n.getString(IUMSConstants.BAD_GUID);
710            }
711
712            throw new IllegalArgumentException(msg);
713        }
714
715        PersistentObject parent = UMSObject.getObject(getPrincipal(),
716                getParentGuid());
717        return parent;
718    }
719
720    /**
721     * Adds a child object to the persistent object container. All persistent
722     * objects can add objects as a container. To override this behavior or
723     * impose restrictions override the add method in a subclass so that e.g.
724     * User.add( object ) is restricted or disallowed in certain ways.
725     * 
726     * @param object Child object to be added to this persistent container.
727     * @throws AccessRightsException if an access rights exception occurs.
728     * @throws EntryAlreadyExistsException if the entry already exists.
729     * @throws UMSException if fail to add the given child object to the 
730     *         container. Possible causes include
731     *         <code>EntryAlreadyExists</code>, <code>AccessRights</code>
732     *         violation.
733     *
734     * @supported.api
735     */
736    public void addChild(PersistentObject object) throws AccessRightsException,
737            EntryAlreadyExistsException, UMSException {
738        if (object == null) {
739            String args[] = new String[1];
740
741            args[0] = this.toString();
742            String msg = i18n.getString(IUMSConstants.ADD_NULL_OBJ, args);
743
744            throw new IllegalArgumentException(msg);
745        }
746
747        String idAttr = object.getNamingAttribute();
748        String idValue = null;
749        Attr idAttrObj = object.getAttribute(idAttr);
750        if (idAttrObj != null) {
751            idValue = idAttrObj.getValue();
752        } else {
753            throw new UMSException(BAD_NAMING_ATTR + idAttr);
754        }
755
756        if (idAttr == null || idValue == null || idValue.length() == 0) {
757            String args[] = new String[1];
758
759            args[0] = object.toString();
760            String msg = i18n
761                    .getString(IUMSConstants.COMPOSE_GUID_FAILED, args);
762
763            throw new IllegalArgumentException(msg);
764        }
765
766        String childStr = null;
767
768        if (getGuid().getDn().length() > 0) {
769            childStr = idAttr + "=" + idValue + "," + getGuid().getDn();
770        } else {
771            childStr = idAttr + "=" + idValue;
772        }
773
774        Guid childGuid = new Guid(childStr);
775        object.setGuid(childGuid);
776
777        // Validation was done during the creation of the object
778        // Validation.validateAttributes( object.getAttrSet(),
779        // object.getClass(), this.getGUID() );
780
781        DataLayer.getInstance().addEntry(getPrincipal(), childGuid,
782                object.getAttrSet());
783
784        object.setModSet(null);
785        object.setPrincipal(getPrincipal());
786
787        EntityManager em = EntityManager.getEntityManager();
788        try {
789            em.execute(getPrincipal(), object, m_guid);
790        } catch (UMSException e) {
791            // TODO - we should log error...
792            if (debug.messageEnabled()) {
793                debug.message("PersistentObject.addChild : UMSException : "
794                        + e.getMessage());
795            }
796        }
797    }
798
799    /**
800     * Removes a child object from a persistent object container. It is
801     * important for constraints to be applied in overriding this method in
802     * subclasses of PersistentObject. For example, Organization may choose not
803     * to allow remove( object ) when object is an organization.
804     * 
805     * @param object Child object to be removed.
806     * @throws AccessRightsException if an access rights exception occurs.
807     * @throws EntryNotFoundException if the entry is not found.
808     * @throws UMSException if fail to remove the child object. Possible causes
809     *         includes EntryNotFount, AccessRights violation etc.
810     *
811     * @supported.api
812     */
813    public void removeChild(PersistentObject object)
814            throws AccessRightsException, EntryNotFoundException, UMSException {
815        String childStr;
816        if (object == null) {
817            String args[] = new String[1];
818
819            args[0] = this.toString();
820            String msg = i18n.getString(IUMSConstants.DEL_NULL_OBJ, args);
821            throw new IllegalArgumentException(msg);
822        }
823
824        childStr = object.getGuid().getDn();
825
826        // If this is an in-memory object, attempt to compose the guid
827        // for the child object
828        //
829        if (childStr == null) {
830            String idAttr = object.getNamingAttribute();
831            String idValue = object.getAttribute(idAttr).getValue();
832
833            if (idAttr == null || idValue == null || idValue.length() == 0) {
834                String args[] = new String[1];
835
836                args[0] = object.toString();
837                String msg = i18n.getString(IUMSConstants.COMPOSE_GUID_FAILED,
838                        args);
839
840                throw new IllegalArgumentException(msg);
841            }
842
843            if (getGuid().getDn().length() > 0) {
844                childStr = idAttr + "=" + idValue + "," + getGuid();
845            } else {
846                childStr = idAttr + "=" + idValue;
847            }
848        }
849
850        DN parentEntry = DN.valueOf(getDN());
851        DN childEntry = DN.valueOf(childStr);
852
853        if (!childEntry.isInScopeOf(parentEntry, SearchScope.SUBORDINATES)) {
854            String msg = i18n.getString(IUMSConstants.BAD_CHILD_OBJ);
855            // TODO: need review. Should we throw something
856            // more meaningful
857            throw new IllegalArgumentException(msg);
858        }
859
860        DataLayer.getInstance().deleteEntry(getPrincipal(), new Guid(childStr));
861
862        // TODO: ??? do we need to mark the object that has been deleted
863        // with an invalid session
864        //
865        object.setGuid(new Guid("DELETED"));
866        object.setPrincipal(null);
867    }
868
869    /**
870     * Removes an object given its unique ID. This method expects the given
871     * child ID is a descendant (something being contained) in "this" object
872     * 
873     * @param childGuid Unique entry identification for the child to be removed.
874     * @throws AccessRights if an access rights exception occurs.
875     * @throws EntryNotFoundException if the entry is not found.
876     * @throws UMSException if failure to remove the entry from the persistent
877     *         store. Possible causes include AccessRights violation,
878     *         EntryNotFound etc.
879     *
880     * @supported.api
881     */
882    public void removeChild(Guid childGuid) throws AccessRightsException,
883            EntryNotFoundException, UMSException {
884        DN parentEntry = DN.valueOf(getDN());
885        DN childEntry = DN.valueOf(childGuid.getDn());
886
887        if (!childEntry.isInScopeOf(parentEntry, SearchScope.SUBORDINATES)) {
888            String msg = i18n.getString(IUMSConstants.BAD_CHILD_OBJ);
889
890            throw new IllegalArgumentException(msg);
891        }
892
893        DataLayer.getInstance().deleteEntry(getPrincipal(), childGuid);
894    }
895
896    /**
897     * Remove itself from the persistent store. This method removes the object
898     * at hand from the persistent storage but keeps its internal data so that
899     * the ums client can save it to somewhere else or make reference to its
900     * internal data
901     * <P>
902     * 
903     * @throws AccessRights
904     *             Exception if an access rights exception occurs.
905     * @throws EntryNotFoundException
906     *             if the entry is not found
907     * @throws UMSException
908     *             from UMSSession.removeObject( principal, guid)
909     *
910     * @supported.api
911     */
912    public void remove() throws AccessRightsException, EntryNotFoundException,
913            UMSException {
914        // REVIEW: should we keep this method where an object can delete itself
915        // we don't allow an object to add itself ^%$#@
916
917        // If this is an in memory object with no reference to an entry,
918        // don't do anything
919        //
920        if (m_guid == null || m_principal == null)
921            return;
922
923        // Remove the object from persitent store
924        //
925        DataLayer.getInstance().deleteEntry(getPrincipal(), getGuid());
926
927        // Now reset it as a memory object with no reference to an entry on
928        // the persistent store. Possible use of this object in a move
929        // implementation. Call save(principal, namingAttr, parentGUID) to
930        // achieve
931        // the more functionality
932        //
933        setGuid(null);
934        setPrincipal(null);
935    }
936
937    /**
938     * Gets a string representation of the object
939     * 
940     * @return String representation of the object
941     *
942     * @supported.api
943     */
944    public String toString() {
945        StringBuilder sb = new StringBuilder();
946        sb.append("Object ID        :").append(m_guid).append("\n");
947        sb.append("Naming attribute :").append(getNamingAttribute()).append("\n");
948        sb.append("Class            :").append(getClass().getName()).append("\n");
949        sb.append("Principal        :").append(m_principal).append("\n");
950        sb.append("Attribute Set    :").append(m_attrSet).append("\n");
951        return sb.toString();
952    }
953
954    /**
955     * Gets the parent guid
956     * 
957     * @return string representation of the parent guid public String
958     *         getParentID() { return getParentID(null); }
959     */
960
961    /**
962     * Gets the parent guid
963     * 
964     * @return String representation of the parent guid
965     */
966    public Guid getParentGuid() {
967        if (m_guid == null || m_principal == null) {
968            return null;
969        }
970
971        DN dn = DN.valueOf(getDN());
972        return new Guid(dn.parent().toString());
973    }
974
975    /**
976     * Gets the immediate children, subject to search filter constraints. Only
977     * the IDs and object classes of each child are returned.
978     * <P>
979     * 
980     * @param filter
981     *            Search filter
982     * @param searchControl
983     *            Search control
984     * @return Result child IDs in Vector
985     * @throws InvalidSearchFilterException
986     *             if the search filter is invalid
987     * @throws UMSException
988     *             on searching for immediate children in the container
989     *
990     * @supported.api
991     */
992    public SearchResults getChildren(String filter, SearchControl searchControl)
993            throws InvalidSearchFilterException, UMSException {
994        // default is one level search scope in getChildren
995        //
996        int scope = SearchControl.SCOPE_ONE;
997        if (searchControl != null) {
998            scope = searchControl.getSearchScope(scope);
999        }
1000
1001        SearchResults results = DataLayer.getInstance().searchIDs(
1002                getPrincipal(), getGuid(), scope, filter, searchControl);
1003        results.setPrincipal(getPrincipal());
1004        return results;
1005    }
1006
1007    /**
1008     * Gets the immediate children, returning only specified attributes
1009     * 
1010     * @param filter
1011     *            Search filter
1012     * @param resultAttributeNames
1013     *            Names of attributes to retrieve
1014     * @param searchControl
1015     *            Search control object
1016     * @return SearchResults
1017     * @throws InvalidSearchFilterException
1018     *             on invalid search filter
1019     * @throws UMSException
1020     *             on failure with searhing
1021     * 
1022     * @supported.api
1023     */
1024    public SearchResults getChildren(String filter,
1025            String[] resultAttributeNames, SearchControl searchControl)
1026            throws InvalidSearchFilterException, UMSException {
1027
1028        // default is one level search scope in getChildren
1029        //
1030        int scope = SearchControl.SCOPE_ONE;
1031        if (searchControl != null) {
1032            scope = searchControl.getSearchScope(scope);
1033        }
1034
1035        SearchResults searchResults = DataLayer.getInstance().search(
1036                getPrincipal(), getGuid(), scope, filter, resultAttributeNames,
1037                false, searchControl);
1038        searchResults.setPrincipal(getPrincipal());
1039
1040        return searchResults;
1041    }
1042
1043    /**
1044     * Gets all immediate children under current node based on search criteria
1045     * specified in template, and returns attributes specified there. Search
1046     * behavior is controlled by searchControl.
1047     * <P>
1048     * 
1049     * Returning attributes are determined by the search template
1050     * <P>
1051     * 
1052     * @param template
1053     *            Search template
1054     * @param searchControl
1055     *            Search control, use default setting if searchControl == null
1056     * @throws UMSException
1057     *             on failure with searching
1058     *
1059     * @supported.api
1060     */
1061    public SearchResults getChildren(SearchTemplate template,
1062            SearchControl searchControl) throws UMSException {
1063        return getChildren(template.getSearchFilter(), template
1064                .getAttributeSet().getAttributeNames(), searchControl);
1065    }
1066
1067    /**
1068     * Gets only the IDs and object classes of all objects at the current level
1069     * and below which match the search filter.
1070     * 
1071     * @param filter
1072     *            Search filter
1073     * @param searchControl
1074     *            Search control object
1075     * @throws InvalidSearchFilterException
1076     *             on invalid search filter
1077     * @throws UMSException
1078     *             on failure with searching
1079     *
1080     * @supported.api
1081     */
1082    public SearchResults search(String filter, SearchControl searchControl)
1083            throws InvalidSearchFilterException, UMSException {
1084
1085        // Default search scope is Subtree type of search
1086        //
1087        int scope = SearchControl.SCOPE_SUB;
1088        if (searchControl != null) {
1089            scope = searchControl.getSearchScope(scope);
1090        }
1091
1092        SearchResults results = DataLayer.getInstance().searchIDs(
1093                getPrincipal(), getGuid(), scope, filter, searchControl);
1094        results.setPrincipal(getPrincipal());
1095        return results;
1096    }
1097
1098    /**
1099     * Gets the specified attributes of all objects at the current level and
1100     * below which match the search filter.
1101     * 
1102     * @param filter
1103     *            Search filter
1104     * @param resultAttributeNames
1105     *            Names of attributes to retrieve
1106     * @param searchControl
1107     *            Search control object
1108     * @return SearchResults
1109     * @throws InvalidSearchFilterException
1110     *             on invalid search filter
1111     * @throws UMSException
1112     *             on failure with searching
1113     * 
1114     *
1115     * @supported.api
1116     */
1117    public SearchResults search(String filter, String[] resultAttributeNames,
1118            SearchControl searchControl) throws InvalidSearchFilterException,
1119            UMSException {
1120
1121        // Default search scope is Subtree type of search
1122        //
1123        int scope = SearchControl.SCOPE_SUB;
1124        if (searchControl != null) {
1125            scope = searchControl.getSearchScope(scope);
1126        }
1127
1128        SearchResults results = DataLayer.getInstance().search(getPrincipal(),
1129                getGuid(), scope, filter, resultAttributeNames, false,
1130                searchControl);
1131        results.setPrincipal(getPrincipal());
1132        return results;
1133    }
1134
1135    /**
1136     * Gets the attributes specified in the template for all objects at the
1137     * current level and below which match the search filter in the template.
1138     * Search behavior is controlled by searchControl.
1139     * <P>
1140     * 
1141     * @param template
1142     *            Search template
1143     * @param searchControl
1144     *            Search control, use default setting if searchControl == null
1145     * @throws UMSException
1146     *             on failure to search
1147     *
1148     * @supported.api
1149     */
1150    public SearchResults search(SearchTemplate template,
1151            SearchControl searchControl) throws UMSException {
1152        return search(template.getSearchFilter(), template.getAttributeSet()
1153                .getAttributeNames(), searchControl);
1154    }
1155
1156    /**
1157     * Gets the DN of the entity
1158     * 
1159     * @return String representing the distinguished name of the entry
1160     * 
1161     */
1162    public String getDN() {
1163        if (m_guid != null)
1164            return m_guid.getDn();
1165        else
1166            return null;
1167    }
1168
1169    /**
1170     * Sets the GUID of the entity; used within the package
1171     * 
1172     * @param guid
1173     *            String representation of guid
1174     */
1175    protected void setGuid(Guid guid) {
1176
1177        m_guid = guid;
1178    }
1179
1180    /**
1181     * Return the authenticated principal that is used to instantiate this
1182     * object
1183     * 
1184     * @return authenticated principal that is used to instantiate this object,
1185     *         return null if no authenticated principal is associated with this
1186     *         object
1187     */
1188    Principal getPrincipal() {
1189        return m_principal;
1190    }
1191
1192    /**
1193     * Set current authenticated session; used within the package
1194     * 
1195     * @param session
1196     *            A valid authenticated session
1197     */
1198    void setPrincipal(Principal principal) {
1199        m_principal = principal;
1200    }
1201
1202    /**
1203     * Sets the attribute set
1204     * 
1205     * @param attrSet
1206     *            The attribute set to be assigned as a reference (not a deep
1207     *            copy)
1208     */
1209    protected void setAttrSet(AttrSet attrSet) {
1210        m_attrSet = attrSet;
1211    }
1212
1213    /**
1214     * Gets the attribute set as a reference, not a deep copy
1215     * 
1216     * @return The in-memory attribute set
1217     */
1218    protected AttrSet getAttrSet() {
1219        return m_attrSet;
1220    }
1221
1222    /**
1223     * Checks if javaclass of the persistent object is the expected class
1224     * defined in its objectclass attribute.
1225     * 
1226     * @throws UMSException
1227     *             when the objectclass maps to a javaclass different from the
1228     *             one being constructed.
1229     * @see com.iplanet.ums.TempateManager#getJavaClassForEntry
1230     */
1231    void verifyClass() throws UMSException {
1232        Class expectedClass = 
1233            TemplateManager.getTemplateManager().getJavaClassForEntry(
1234                    this.getGuid().getDn(), this.getAttrSet());
1235
1236        // TODO: need to be reviewed and see if subclasses of entity are
1237        // allowed.
1238        // e.g. PersistentObject -> User -> MailUser kind of inheritence, do we
1239        // accept the formation of User class for MailUser.
1240        //
1241        if (this.getClass() != expectedClass) {
1242            String msg = i18n.getString(IUMSConstants.UNMATCHED_CLASS);
1243            // TODO: review for better exception
1244            throw new IllegalArgumentException(msg);
1245        }
1246    }
1247
1248    /**
1249     * Maps to a DN from naming attribute value of a persistent object.
1250     * 
1251     * @param namingAttribute Naming attribute of the object.
1252     * @param name Naming attribute value of the object.
1253     * @param parentID Array of its parent names, all assumed to take 
1254     *        <code>o</code> as the naming attributes.
1255     */
1256    static public String idToDN(
1257        String namingAttribute,
1258        String name,
1259        String[] parentID
1260    ) {
1261        StringBuilder sb = new StringBuilder();
1262
1263        sb.append(namingAttribute).append("=").append(name);
1264        for (int i = 0; i < parentID.length; i++) {
1265            if (parentID[i] != null) {
1266                // TODO: ??? This is hardcoded to take "o=something" as the
1267                // parent node(s). Needs a flexible scheme to handle
1268                // flexible DIT.
1269                sb.append(",o=").append(parentID[i]);
1270            }
1271        }
1272
1273        return sb.toString();
1274    }
1275
1276    /**
1277     * Maps a dn to guid
1278     * <P>
1279     * TODO: Not yet implemented
1280     * <P>
1281     */
1282    static String dnToGuid(String dn) {
1283        // TODO: Need to fill in base 64 encoding <P>
1284        //
1285        return dn;
1286    }
1287
1288    /**
1289     * Maps a guid to dn
1290     * <P>
1291     * TODO: Not yet implemented
1292     * <P>
1293     */
1294    static String guidToDN(String guid) {
1295        // TODO: Need to fill in base 64 encoding <P>
1296        //
1297        return guid;
1298    }
1299
1300    /**
1301     * Reads in the object from persistent store, assuming that the guid and
1302     * session are valid
1303     * 
1304     * @throws UMSException
1305     *             on failure in reading the object from persistent store
1306     */
1307    synchronized private void read() throws UMSException {
1308        if (m_principal == null || m_guid == null) {
1309            // TODO: there should be some warning to the client here
1310            return;
1311        }
1312
1313        m_attrSet = DataLayer.getInstance().read(getPrincipal(), getGuid());
1314    }
1315
1316    private void checkCache() {
1317        if (m_principal != null && m_guid != null && m_attrSet == null) {
1318            try {
1319                read();
1320            } catch (UMSException e) {
1321                // TODO: there should be some warning to the client here
1322                if (debug.messageEnabled()) {
1323                    debug.message("PersistentObject.checkCache() : " +
1324                            "UMSException : " + e.getMessage());
1325                }
1326            }
1327        }
1328    }
1329
1330    /**
1331     * Internal use only, set the internal modset. Can be used to nullify the
1332     * internal state of m_modSet
1333     * 
1334     * @param modSet
1335     *            Modification Set to be used for the internal
1336     *            <code>modSet</code>.
1337     */
1338    void setModSet(Collection<Modification> modSet) {
1339        m_modSet = modSet;
1340    }
1341
1342    /**
1343     * Checks if this object is a member of an IMembership. Roles and Groups
1344     * implement IMembership
1345     * 
1346     * @param im
1347     *            Role or Group against which the membership is to be checked
1348     * @return <code>true</code> if this object is a member of the
1349     *         IMembership, <code>false</code> otherwise
1350     * @throws UMSException
1351     *             propagates any exception from the datalayer
1352     *
1353     * @supported.api
1354     */
1355    public boolean isMemberOf(IMembership im) throws UMSException {
1356        return im.hasMember(getGuid());
1357    }
1358
1359    /**
1360     * Gets the list of GUIDS roles assosciated with this object
1361     * 
1362     * @return list that lets iterating over role GUIDs
1363     * @throws UMSException
1364     *             propagates any exception from the datalayer
1365     *
1366     * @supported.api
1367     */
1368    public Collection getRoles() throws UMSException {
1369        ArrayList roleList = new ArrayList();
1370        Attr roleAttr = getAttribute(COMPUTED_MEMBER_ATTR_NAME);
1371        if (roleAttr != null && roleAttr.getStringValues() != null) {
1372            roleList.addAll(Arrays.asList(roleAttr.getStringValues()));
1373        }
1374        return roleList;
1375    }
1376
1377    /**
1378     * Returns all the ACIs of this object.
1379     * 
1380     * @return collecion of ACIs of this object.
1381     * @throws ACIParseException if any error
1382     *
1383     * @supported.api
1384     */
1385    public Collection getACI() throws ACIParseException, UMSException {
1386        Collection acis = new ArrayList();
1387        Attr aciAttr = getAttribute(ACI.ACI);
1388        if (aciAttr != null) {
1389            String[] aciTexts = aciAttr.getStringValues();
1390            int size = aciTexts.length;
1391            for (int i = 0; i < size; i++) {
1392                acis.add(ACI.valueOf(aciTexts[i]));
1393            }
1394        }
1395        return acis;
1396    }
1397
1398    /**
1399     * Returns all the ACIs of this object with the given name.
1400     * 
1401     * @param name Name of the ACI to get.
1402     * @return collecion of ACIs of this object.
1403     * @throws ACIParseException in case of any error
1404     *
1405     * @supported.api
1406     */
1407    public Collection getACI(String name) throws ACIParseException,
1408            UMSException {
1409        Collection acis = new ArrayList();
1410        Attr aciAttr = getAttribute(ACI.ACI);
1411        if (aciAttr != null) {
1412            String[] aciTexts = aciAttr.getStringValues();
1413            int size = aciTexts.length;
1414            for (int i = 0; i < size; i++) {
1415                ACI aci = ACI.valueOf(aciTexts[i]);
1416                if (aci.getName().equals(name)) {
1417                    acis.add(aci);
1418                }
1419            }
1420        }
1421        return acis;
1422    }
1423
1424    /**
1425     * Adds an ACI to this object.
1426     * 
1427     * @param aci ACI added to be added to this object.
1428     * @throws AccessRightsException if an access rights exception occurs.
1429     * @throws UMSException if any error
1430     *
1431     * @supported.api
1432     */
1433    public void addACI(ACI aci) throws AccessRightsException, UMSException {
1434        Attr attr = new Attr(ACI.ACI, aci.toString());
1435        modify(attr, ModificationType.ADD);
1436        save();
1437    }
1438
1439    /**
1440     * Deletes an ACI of this object
1441     * 
1442     * @param aci ACI to be deleted.
1443     * @throws AccessRightsException if an access rights exception occurs.
1444     * @throws UMSException if any error.
1445     *
1446     * @supported.api
1447     */
1448    public void deleteACI(ACI aci) throws AccessRightsException, UMSException {
1449        Attr attr = new Attr(ACI.ACI, aci.getACIText());
1450        modify(attr, ModificationType.DELETE);
1451        save();
1452    }
1453
1454    /**
1455     * Replaces an ACI of this object
1456     * 
1457     * @param oldACI ACI to be replaced.
1458     * @param newACI the new ACI.
1459     * @throws AccessRightsException if an access rights exception occurs.
1460     * @throws UMSException if any error.
1461     *
1462     * @supported.api
1463     */
1464    public void replaceACI(ACI oldACI, ACI newACI)
1465            throws AccessRightsException, UMSException {
1466        Attr attr = new Attr(ACI.ACI, oldACI.getACIText());
1467        modify(attr, ModificationType.DELETE);
1468        attr = new Attr(ACI.ACI, newACI.toString());
1469        modify(attr, ModificationType.ADD);
1470        save();
1471    }
1472
1473    /**
1474     * Adds value for an attribute and saves the change in the database.
1475     * 
1476     * @param token Authenticated prinicpal's single sign on token.
1477     * @param guid Identifiation of the entry to which to add the attribute
1478     *        value.
1479     * @param name Name of the attribute to which value is being added.
1480     * @param value Value to be added to the attribute.
1481     * @throws UMSException if any exception from the data layer.
1482     *
1483     * @supported.api
1484     */
1485    public static void addAttributeValue(
1486        SSOToken token,
1487        Guid guid,
1488        String name,
1489        String value
1490    ) throws UMSException {
1491        if (guid == null) {
1492            throw new IllegalArgumentException(i18n
1493                    .getString(IUMSConstants.NULL_GUIDS));
1494        }
1495        if (token == null) {
1496            throw new IllegalArgumentException(i18n
1497                    .getString(IUMSConstants.NULL_TOKEN));
1498        }
1499        try {
1500            SSOTokenManager.getInstance().validateToken(token);
1501        } catch (SSOException se) {
1502            throw new UMSException(i18n.getString(IUMSConstants.INVALID_TOKEN),
1503                    se);
1504        }
1505
1506        Attr attr = new Attr(name, value);
1507        attr = null;
1508        Validation.validateAttribute(attr, UMSObject.getObject(token, guid)
1509                .getClass(), guid);
1510        try {
1511            DataLayer.getInstance().addAttributeValue(token.getPrincipal(),
1512                    guid, name, value);
1513        } catch (SSOException se) {
1514            throw new UMSException(i18n.getString(IUMSConstants.BAD_TOKEN_HDL),
1515                    se);
1516        }
1517    }
1518
1519    /**
1520     * Removes value for an attribute and saves the change in the database.
1521     * 
1522     * @param token Authenticated prinicpal's single sign on token.
1523     * @param guid Identification of the entry from which to remove the
1524     *        attribute value.
1525     * @param name Name of the attribute from which value is being removed.
1526     * @param value Value to be removed from the attribute.
1527     * @throws UMSException if any exception from the data layer.
1528     *
1529     * @supported.api
1530     */
1531    public static void removeAttributeValue(SSOToken token, Guid guid,
1532            String name, String value) throws UMSException {
1533        if (guid == null) {
1534            throw new IllegalArgumentException(i18n
1535                    .getString(IUMSConstants.NULL_GUIDS));
1536        }
1537        if (token == null) {
1538            throw new IllegalArgumentException(i18n
1539                    .getString(IUMSConstants.NULL_TOKEN));
1540        }
1541        try {
1542            SSOTokenManager.getInstance().validateToken(token);
1543        } catch (SSOException se) {
1544            throw new UMSException(i18n.getString(IUMSConstants.INVALID_TOKEN),
1545                    se);
1546        }
1547
1548        try {
1549            DataLayer.getInstance().removeAttributeValue(token.getPrincipal(),
1550                    guid, name, value);
1551        } catch (SSOException se) {
1552            throw new UMSException(i18n.getString(IUMSConstants.BAD_TOKEN_HDL),
1553                    se);
1554        }
1555    }
1556
1557    /**
1558     * Check if the object is persistent in the system
1559     * 
1560     * @return true if the object is persistent in the system and false
1561     *         otherwise
1562     */
1563    protected boolean isPersistent() {
1564        return m_principal != null && m_guid != null
1565                && m_guid.getDn().length() > 0;
1566    }
1567
1568    /**
1569     * Find the names of attributes not read from data store so far
1570     * 
1571     * @param attrNames
1572     *            names of attributes to get
1573     * @return collection of names of attributes not read from data store so far
1574     */
1575    private Collection findAttributesNotRead(String[] attrNames) {
1576        ArrayList attributesNotInCache = new ArrayList();
1577        if (m_attrSet == null) {
1578            m_attrSet = new AttrSet();
1579        }
1580        if (m_nullAttributes == null) {
1581            m_nullAttributes = new ArrayList();
1582        }
1583        int length = attrNames.length;
1584        for (int i = 0; i < length; i++) {
1585            if ((m_attrSet.getAttribute(attrNames[i]) == null)
1586                    && !m_nullAttributes.contains(attrNames[i])) {
1587                attributesNotInCache.add(attrNames[i]);
1588            }
1589        }
1590        return attributesNotInCache;
1591    }
1592
1593    /**
1594     * Find whether the attribute was not read from data store so far
1595     * 
1596     * @param attrName
1597     *            name of attribute to check
1598     * @return <code>true</code> if the attribute was not read, otherwise
1599     *         <code>false</code>
1600     */
1601    private boolean isAttributeNotRead(String attrName) {
1602        boolean attributeNotRead = false;
1603        if (m_attrSet == null) {
1604            m_attrSet = new AttrSet();
1605        }
1606        if (m_nullAttributes == null) {
1607            m_nullAttributes = new ArrayList();
1608        }
1609        if ((m_attrSet.getAttribute(attrName) == null)
1610                && !m_nullAttributes.contains(attrName)) {
1611            attributeNotRead = true;
1612        }
1613        return attributeNotRead;
1614    }
1615
1616    /**
1617     * Read the attributes from data store
1618     * 
1619     * @param attrNames
1620     *            names of attributes to get
1621     * @return collection of Attr read from data store
1622     */
1623    private Collection readAttributesFromDataStore(Collection attrNames)
1624            throws UMSException {
1625        Collection attributes = DataLayer.getInstance().getAttributes(
1626                getPrincipal(), getGuid(), attrNames);
1627        if (attributes == null) {
1628            String[] args = { getDN() };
1629            throw new UMSException(i18n.getString(
1630                    IUMSConstants.READ_ATTRIBUTES_ERROR, args));
1631        }
1632        Collection foundAttributes = new ArrayList();
1633        if (m_attrSet == null) {
1634            m_attrSet = new AttrSet();
1635        }
1636        if (m_nullAttributes == null) {
1637            m_nullAttributes = new ArrayList();
1638        }
1639        Iterator iter = attributes.iterator();
1640        while (iter.hasNext()) {
1641            Attr attr = (Attr) iter.next();
1642            foundAttributes.add(attr.getName());
1643            m_attrSet.replace(attr);
1644        }
1645        iter = attrNames.iterator();
1646        while (iter.hasNext()) {
1647            String attrName = (String) iter.next();
1648            if (!foundAttributes.contains(attrName)
1649                    && !m_nullAttributes.contains(attrName)) {
1650                m_nullAttributes.add(attrName);
1651            }
1652        }
1653        return attributes;
1654    }
1655
1656    /**
1657     * Read the attribute from data store
1658     * 
1659     * @param attrName
1660     *            names of attributes to get
1661     * @return Attr read from datastore
1662     */
1663    private Attr readAttributeFromDataStore(String attrName) throws UMSException {
1664        Attr attr = DataLayer.getInstance().getAttribute(getPrincipal(),
1665                getGuid(), attrName);
1666        if (m_attrSet == null) {
1667            m_attrSet = new AttrSet();
1668        }
1669        if (m_nullAttributes == null) {
1670            m_nullAttributes = new ArrayList();
1671        }
1672        if (attr != null) {
1673            m_attrSet.replace(attr);
1674        } else if (!m_nullAttributes.contains(attrName)) {
1675            m_nullAttributes.add(attrName);
1676        }
1677        return attr;
1678    }
1679
1680    /**
1681     * Get the attribute from cache, does not read data store
1682     * 
1683     * @param attrName
1684     *            name of attribute to get
1685     * @return Attr read from cache
1686     */
1687    private Attr getAttributeFromCache(String attrName) {
1688        Attr attr = null;
1689        if (m_attrSet != null) {
1690            attr = m_attrSet.getAttribute(attrName);
1691        }
1692        return attr;
1693    }
1694
1695    /**
1696     * Authenticated session in constructing the object
1697     * 
1698     * @serial
1699     */
1700    private Principal m_principal;
1701
1702    /**
1703     * Identification of the object
1704     * 
1705     * @serial
1706     */
1707    private Guid m_guid;
1708
1709    /**
1710     * Internal cache for attributes
1711     * 
1712     * @serial
1713     */
1714    private AttrSet m_attrSet;
1715
1716    /**
1717     * Internal cache for attributes read from directory and found to be null
1718     * 
1719     * @serial
1720     */
1721    private ArrayList m_nullAttributes;
1722
1723    /**
1724     * Internal cache for changes made to the object
1725     * 
1726     * @serial
1727     */
1728    private Collection<Modification> m_modSet;
1729
1730    /**
1731     * Internal naming attribute (ex: "ou")
1732     * 
1733     * @serial
1734     */
1735    private String m_namingAttribute = null;
1736}