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: DataLayer.java,v 1.19 2009/11/20 23:52:52 ww203982 Exp $
026 *
027 */
028
029/**
030 * Portions Copyrighted [2011] [ForgeRock AS]
031 */
032package com.iplanet.ums;
033
034import com.iplanet.am.util.SystemProperties;
035import com.iplanet.services.ldap.Attr;
036import com.iplanet.services.ldap.AttrSet;
037import com.iplanet.services.ldap.DSConfigMgr;
038import com.iplanet.services.ldap.LDAPServiceException;
039import com.iplanet.services.ldap.LDAPUser;
040import com.iplanet.services.ldap.ModSet;
041import com.iplanet.services.ldap.ServerInstance;
042import com.iplanet.services.ldap.event.EventService;
043import com.iplanet.services.util.I18n;
044import com.sun.identity.common.LDAPConnectionPool;
045import com.sun.identity.common.ShutdownListener;
046import com.sun.identity.common.ShutdownManager;
047import com.sun.identity.security.ServerInstanceAction;
048import com.sun.identity.shared.debug.Debug;
049import java.security.AccessController;
050import java.util.ArrayList;
051import java.util.Arrays;
052import java.util.Collection;
053import java.util.Collections;
054import java.util.HashMap;
055import java.util.HashSet;
056import java.util.Iterator;
057import java.util.StringTokenizer;
058import com.sun.identity.shared.ldap.LDAPAttribute;
059import com.sun.identity.shared.ldap.LDAPAttributeSet;
060import com.sun.identity.shared.ldap.LDAPBind;
061import com.sun.identity.shared.ldap.LDAPConnection;
062import com.sun.identity.shared.ldap.LDAPControl;
063import com.sun.identity.shared.ldap.LDAPEntry;
064import com.sun.identity.shared.ldap.LDAPException;
065import com.sun.identity.shared.ldap.LDAPModification;
066import com.sun.identity.shared.ldap.LDAPRequestParser;
067import com.sun.identity.shared.ldap.LDAPSchema;
068import com.sun.identity.shared.ldap.LDAPSchemaElement;
069import com.sun.identity.shared.ldap.LDAPSearchConstraints;
070import com.sun.identity.shared.ldap.LDAPAddRequest;
071import com.sun.identity.shared.ldap.LDAPDeleteRequest;
072import com.sun.identity.shared.ldap.LDAPModifyRequest;
073import com.sun.identity.shared.ldap.LDAPModifyRDNRequest;
074import com.sun.identity.shared.ldap.LDAPSearchRequest;
075import com.sun.identity.shared.ldap.LDAPSearchResults;
076import com.sun.identity.shared.ldap.LDAPSortKey;
077import com.sun.identity.shared.ldap.controls.LDAPProxiedAuthControl;
078import com.sun.identity.shared.ldap.controls.LDAPSortControl;
079import com.sun.identity.shared.ldap.controls.LDAPVirtualListControl;
080
081/**
082 * DataLayer (A PACKAGE SCOPE CLASS) to access LDAP or other database
083 * 
084 * TODO: 1. Needs to subclass and isolate the current implementation of
085 * DataLayer as DSLayer for ldap specific operations 2. Improvements needed for
086 * _ldapPool: destroy(), initial bind user, tunning for MIN and MAX initial
087 * settings etc 3. May choose to extend implementation of _ldapPool from
088 * LDAPConnectionPool so that there is load balance between connections. Also
089 * _ldapPool may be implemented with a HashTable of (host,port) for mulitple
090 * pools of connections for mulitple (host,port) to DS servers instead of single
091 * host and port.
092 * 
093 * @supported.api
094 */
095public class DataLayer implements java.io.Serializable {
096
097    /**
098     * Static section to retrieve the debug object.
099     */
100    private static Debug debug;
101
102    private static I18n i18n = I18n.getInstance(IUMSConstants.UMS_PKG);
103
104    /**
105     * Default minimal connections if none is defined in configuration
106     */
107
108    /**
109     * Default maximum connections if none is defined in configuration
110     */
111    static final int MAX_CONN = 20;
112
113    /**
114     * Default maximum backlog queue size
115     */
116    static final int MAX_BACKLOG = 100;
117
118    static final String LDAP_MAXBACKLOG = "maxbacklog";
119
120    static final String LDAP_RELEASECONNBEFORESEARCH =
121        "releaseconnectionbeforesearchcompletes";
122
123    static final String LDAP_REFERRAL = "referral";
124
125    private static int replicaRetryNum = 1;
126
127    private static long replicaRetryInterval = 1000;
128
129    private static final String LDAP_CONNECTION_NUM_RETRIES = 
130        "com.iplanet.am.ldap.connection.num.retries";
131
132    private static final String LDAP_CONNECTION_RETRY_INTERVAL = 
133        "com.iplanet.am.ldap.connection.delay.between.retries";
134
135    private static final String LDAP_CONNECTION_ERROR_CODES = 
136        "com.iplanet.am.ldap.connection.ldap.error.codes.retries";
137
138    private static int connNumRetry = 3;
139
140    private static int connRetryInterval = 1000;
141
142    private static HashSet retryErrorCodes = new HashSet();
143    
144    static {
145        debug = Debug.getInstance(IUMSConstants.UMS_DEBUG);
146        initConnectionParams();
147    }
148    
149    public static void initConnectionParams() {
150        String numRetryStr = SystemProperties.get(LDAP_CONNECTION_NUM_RETRIES);
151        if (numRetryStr != null) {
152            try {
153                connNumRetry = Integer.parseInt(numRetryStr);
154            } catch (NumberFormatException e) {
155                if (debug.warningEnabled()) {
156                    debug.warning("Invalid value for "
157                            + LDAP_CONNECTION_NUM_RETRIES);
158                }
159            }
160        }
161
162        String retryIntervalStr = SystemProperties
163                .get(LDAP_CONNECTION_RETRY_INTERVAL);
164        if (retryIntervalStr != null) {
165            try {
166                connRetryInterval = Integer.parseInt(retryIntervalStr);
167            } catch (NumberFormatException e) {
168                if (debug.warningEnabled()) {
169                    debug.warning("Invalid value for "
170                            + LDAP_CONNECTION_RETRY_INTERVAL);
171                }
172            }
173        }
174
175        String retryErrs = SystemProperties.get(LDAP_CONNECTION_ERROR_CODES);
176        if (retryErrs != null) {
177            StringTokenizer stz = new StringTokenizer(retryErrs, ",");
178            while (stz.hasMoreTokens()) {
179                retryErrorCodes.add(stz.nextToken().trim());
180            }
181        }
182
183        if (debug.messageEnabled()) {
184            debug.message("DataLayer: number of retry = " + connNumRetry);
185            debug.message("DataLayer: retry interval = " + connRetryInterval);
186            debug.message("DataLayer: retry error codes = " + retryErrorCodes);
187        }
188    }
189
190    /**
191     * DataLayer constructor
192     */
193    private DataLayer() {
194    }
195
196    /**
197     * Constructor given the extra parameter of guid and pwd identifying an
198     * authenticated principal
199     * 
200     * @param host
201     *            LDAP host
202     * @param port
203     *            LDAP port
204     * @param guid
205     *            Identification of an authenticated principal
206     * @param pwd
207     *            Password for the user
208     */
209    private DataLayer(String id, String pwd, String host, int port)
210        throws UMSException {
211        m_proxyUser = id;
212        m_proxyPassword = pwd;
213        m_host = host;
214        m_port = port;
215
216        initReplicaProperties();
217        initLdapPool();
218    }
219
220    /**
221     * create the singelton DataLayer object if it doesn't exist already.
222     *
223     * @supported.api
224     */
225    public synchronized static DataLayer getInstance(ServerInstance serverCfg)
226        throws UMSException {
227        // Make sure only one instance of this class is created.
228        if (m_instance == null) {
229            String host = "localhost";
230            int port = 389;
231            String pUser = "";
232            String pPwd = "";
233
234            if (serverCfg != null) {
235                host = serverCfg.getServerName();
236                port = serverCfg.getPort();
237                pUser = serverCfg.getAuthID();
238                pPwd = (String) AccessController
239                        .doPrivileged(new ServerInstanceAction(serverCfg));
240            }
241            m_instance = new DataLayer(pUser, pPwd, host, port);
242
243            // Start the EventService thread if it has not already started.
244            initializeEventService();
245        }
246        return m_instance;
247    }
248
249    /**
250     * create the singelton DataLayer object if it doesn't exist already.
251     * Assumes the server instance for "LDAPUser.Type.AUTH_PROXY".
252     *
253     * @supported.api
254     */
255    public static DataLayer getInstance() throws UMSException {
256        // Make sure only one instance of this class is created.
257        if (m_instance == null) {
258            try {
259                DSConfigMgr cfgMgr = DSConfigMgr.getDSConfigMgr();
260                ServerInstance serverCfg = cfgMgr
261                        .getServerInstance(LDAPUser.Type.AUTH_PROXY);
262                m_instance = getInstance(serverCfg);
263            } catch (LDAPServiceException ex) {
264                debug.error("Error:  Unable to get server config instance "
265                        + ex.getMessage());
266            }
267        }
268        return m_instance;
269    }
270
271    /**
272     * Get connection from pool. Reauthenticate if necessary
273     * 
274     * @return connection that is available to use.
275     *
276     * @supported.api
277     */
278    public LDAPConnection getConnection(java.security.Principal principal) {
279        if (_ldapPool == null)
280            return null;
281
282        if (debug.messageEnabled()) {
283            debug.message("Invoking _ldapPool.getConnection()");
284        }
285
286        // proxy as given principal
287        LDAPProxiedAuthControl proxyCtrl = new LDAPProxiedAuthControl(principal
288                .getName(), true);
289        LDAPConnection conn = _ldapPool.getConnection();
290        if (debug.messageEnabled()) {
291            debug.message("Got Connection : " + conn);
292        }
293        LDAPSearchConstraints cons = conn.getSearchConstraints();
294        cons.setServerControls(proxyCtrl);
295        conn.setSearchConstraints(cons);
296
297        return conn;
298    }
299
300    /**
301     * Just call the pool method to release the connection so that the given
302     * connection is free for others to use
303     * 
304     * @param conn
305     *            connection in the pool to be released for others to use
306     *
307     * @supported.api
308     */
309    public void releaseConnection(LDAPConnection conn) {
310        if (_ldapPool == null || conn == null)
311            return;
312
313        // reset the original constraints
314        // TODO: check with ldapjdk and see if this is appropriate
315        // to restore the default constraints.
316        //
317        conn.setSearchConstraints(_defaultSearchConstraints);
318
319        // A soft close on the connection. Returns the connection to the pool
320        // and
321        // make it available.
322        if (debug.messageEnabled()) {
323            debug.message("Invoking _ldapPool.close(conn) : " + conn);
324        }
325        _ldapPool.close(conn);
326        if (debug.messageEnabled()) {
327            debug.message("Released Connection : " + conn);
328        }
329    }
330
331    /**
332     * Just call the pool method to release the connection so that the given
333     * connection is free for others to use
334     * 
335     * @param conn
336     *            connection in the pool to be released for others to use
337     * @param ldapErrCode ldap exception error code used to determine failover.
338     *
339     * @supported.api
340     */
341    public void releaseConnection( LDAPConnection conn , int ldapErrCode)
342    {
343        if (_ldapPool == null || conn == null) return;
344
345        // reset the original constraints
346        // TODO: check with ldapjdk and see if this is appropriate
347        //       to restore the default constraints.
348        //
349        conn.setSearchConstraints(_defaultSearchConstraints);
350
351        // A soft close on the connection.
352        // Returns the connection to the pool and
353        // make it available.
354        if (debug.messageEnabled()) {
355            debug.message("Invoking _ldapPool.close(conn,ldapErrCode) : " +
356                conn + ":" + ldapErrCode);
357        }
358        _ldapPool.close( conn, ldapErrCode );
359        if (debug.messageEnabled()) {
360            debug.message("Released Connection:close(conn,ldapErrCode) : " +
361                conn);
362        }
363    }
364
365    /**
366     * Returns String values of the attribute.
367     * 
368     * @param principal Authentication Principal.
369     * @param guid distinguished name.
370     * @param attrName attribute name.
371     *
372     * @supported.api
373     */
374    public String[] getAttributeString(
375        java.security.Principal principal,
376        Guid guid,
377        String attrName
378    ) {
379        String id = guid.getDn();
380        LDAPEntry ldapEntry = null;
381        LDAPSearchRequest request =
382            LDAPRequestParser.parseReadRequest(id);
383        try {
384            ldapEntry = readLDAPEntry(principal, request);
385        } catch (Exception e) {
386            if (debug.warningEnabled()) {
387                debug.warning(
388                        "Exception in DataLayer.getAttributeString for DN: "
389                                + id, e);
390            }
391            return null;
392        }
393        LDAPAttribute attr = ldapEntry.getAttribute(attrName);           
394        return attr.getStringValueArray();
395    }
396
397    /**
398     * Returns <code>Attr</code> from the given attribute name.
399     * 
400     * @param principal Authentication Principal.
401     * @param guid Distinguished name.
402     * @param attrName Attribute name.
403     *
404     * @supported.api
405     */
406    public Attr getAttribute(
407        java.security.Principal principal,
408        Guid guid,
409        String attrName
410    ) {
411        String id = guid.getDn();
412        LDAPEntry ldapEntry = null;
413        try {
414            String[] attrNames = new String[1];
415            attrNames[0] = attrName;
416            LDAPSearchRequest request = LDAPRequestParser.parseReadRequest(id,
417                attrNames);
418            ldapEntry = readLDAPEntry(principal, request);
419        } catch (Exception e) {
420            if (debug.warningEnabled()) {
421                debug.warning("Exception in DataLayer.getAttribute for DN: "
422                        + id, e);
423            }
424            return null;
425        }
426        LDAPAttribute ldapAttr = ldapEntry.getAttribute(attrName);
427        if (ldapAttr == null) {
428            return null;
429        } else {
430            return new Attr(ldapAttr);
431        }
432    }
433
434    /**
435     * Returns attributes for the given attribute names.
436     * 
437     * @param principal Authentication Principal.
438     * @param guid Distinguished name.
439     * @param attrNames Attribute names.
440     * @return collection of Attr.
441     *
442     * @supported.api
443     */
444    public Collection getAttributes(
445        java.security.Principal principal,
446        Guid guid,
447        Collection attrNames
448    ) {
449        Collection attributes = new ArrayList();
450        String id = guid.getDn();
451        LDAPSearchRequest request = LDAPRequestParser.parseReadRequest(id,
452            (String[]) attrNames.toArray(EMPTY_STRING_ARRAY));
453        LDAPEntry ldapEntry = null;
454        try {
455            ldapEntry = readLDAPEntry(principal, request);
456        } catch (Exception e) {
457            if (debug.warningEnabled()) {
458                debug.warning("Exception in DataLayer.getAttributes for DN: "
459                        + id, e);
460            }
461            return null;
462        }
463        if (ldapEntry == null) {
464            debug.warning("No attributes returned may not have " +
465                "permission to read");
466            return Collections.EMPTY_SET;
467        }
468        Iterator iter = attrNames.iterator();
469        while (iter.hasNext()) {
470            String attrName = (String) iter.next();
471            LDAPAttribute ldapAttribute = ldapEntry.getAttribute(attrName);
472            if (ldapAttribute != null) {
473                attributes.add(new Attr(ldapAttribute));
474            }
475        }
476        return attributes;
477    }
478
479    /**
480     * Adds entry to the server.
481     * 
482     * @param principal Authenticated Principal.
483     * @param guid Distinguished name.
484     * @param attrSet attribute set containing name/value pairs.
485     * @exception AccessRightsException if insufficient access>
486     * @exception EntryAlreadyExistsException if the entry already exists.
487     * @exception UMSException if fail to add entry.
488     *
489     * @supported.api
490     */
491    public void addEntry(
492        java.security.Principal principal,
493        Guid guid,
494        AttrSet attrSet
495    ) throws AccessRightsException, EntryAlreadyExistsException, UMSException {
496        LDAPConnection conn = null;
497        String id = guid.getDn();
498        int errorCode = 0;
499
500        try {
501            LDAPEntry entry = new LDAPEntry(id, attrSet.toLDAPAttributeSet());
502            LDAPAddRequest request = LDAPRequestParser.parseAddRequest(entry);
503            int retry = 0;
504            while (retry <= connNumRetry) {
505                if (debug.messageEnabled()) {
506                    debug.message("DataLayer.addEntry retry: " + retry);
507                }
508
509                try {
510                    conn = getConnection(principal);
511                    conn.add(request);
512                    return;
513                } catch (LDAPException e) {
514                    errorCode = e.getLDAPResultCode();
515                    releaseConnection(conn, errorCode);
516                    conn = null;
517                    if (!retryErrorCodes.contains("" + e.getLDAPResultCode())
518                            || retry == connNumRetry) {
519                        throw e;
520                    }
521                    retry++;
522                    try {
523                        Thread.sleep(connRetryInterval);
524                    } catch (InterruptedException ex) {
525                    }
526                }
527            }
528        } catch (LDAPException e) {
529            if (debug.warningEnabled()) {
530                debug.warning("Exception in DataLayer.addEntry for DN: " + id,
531                        e);
532            }
533            errorCode = e.getLDAPResultCode();
534            String[] args = { id };
535            switch (errorCode) {
536            case LDAPException.ENTRY_ALREADY_EXISTS:
537                throw new EntryAlreadyExistsException(i18n.getString(
538                        IUMSConstants.ENTRY_ALREADY_EXISTS, args), e);
539            case LDAPException.INSUFFICIENT_ACCESS_RIGHTS:
540                throw new AccessRightsException(i18n.getString(
541                        IUMSConstants.INSUFFICIENT_ACCESS_ADD, args), e);
542            default:
543                throw new UMSException(i18n.getString(
544                        IUMSConstants.UNABLE_TO_ADD_ENTRY, args), e);
545            }
546        } finally {
547            if (conn != null) {
548                releaseConnection(conn);
549            }
550        }
551    }
552
553    /**
554     * Delete entry from the server
555     * 
556     * @param guid
557     *            globally unique identifier for the entry
558     * @exception AccessRightsException
559     *                insufficient access
560     * @exception EntryNotFoundException
561     *                if the entry is not found
562     * @exception UMSException
563     *                Fail to delete the entry
564     *
565     * @supported.api
566     */
567    public void deleteEntry(java.security.Principal principal, Guid guid)
568            throws AccessRightsException, EntryNotFoundException, UMSException {
569        if (guid == null) {
570            String msg = i18n.getString(IUMSConstants.BAD_ID);
571            throw new IllegalArgumentException(msg);
572        }
573        LDAPConnection conn = null;
574        String id = guid.getDn();
575        int errorCode = 0;
576
577        try {
578            LDAPDeleteRequest request =
579                LDAPRequestParser.parseDeleteRequest(id);
580            int retry = 0;
581            while (retry <= connNumRetry) {
582                if (debug.messageEnabled()) {
583                    debug.message("DataLayer.deleteEntry retry: " + retry);
584                }
585
586                try {
587                    conn = getConnection(principal);
588                    conn.delete(request);
589                    return;
590                } catch (LDAPException e) {
591                    errorCode = e.getLDAPResultCode();
592                    releaseConnection(conn, errorCode);
593                    conn = null;
594                    if (!retryErrorCodes.contains("" + e.getLDAPResultCode())
595                            || retry == connNumRetry) {
596                        throw e;
597                    }
598                    retry++;
599                    try {
600                        Thread.sleep(connRetryInterval);
601                    } catch (InterruptedException ex) {
602                    }
603                }
604            }
605        } catch (LDAPException e) {
606            debug.error("Exception in DataLayer.deleteEntry for DN: " + id, e);
607            errorCode = e.getLDAPResultCode();
608            String[] args = { id };
609            switch (errorCode) {
610            case LDAPException.NO_SUCH_OBJECT:
611                throw new EntryNotFoundException(i18n.getString(
612                        IUMSConstants.ENTRY_NOT_FOUND, args), e);
613            case LDAPException.INSUFFICIENT_ACCESS_RIGHTS:
614                throw new AccessRightsException(i18n.getString(
615                        IUMSConstants.INSUFFICIENT_ACCESS_DELETE, args), e);
616            default:
617                throw new UMSException(i18n.getString(
618                        IUMSConstants.UNABLE_TO_DELETE_ENTRY, args), e);
619            }
620        } finally {
621            if (conn != null) {
622                releaseConnection(conn);
623            }
624        }
625    }
626
627    /**
628     * Read an ldap entry
629     * 
630     * @param guid
631     *            globally unique identifier for the entry
632     * @return an attribute set representing the entry in ldap, all non
633     *         operational attributes are read
634     * @exception EntryNotFoundException
635     *                if the entry is not found
636     * @exception UMSException
637     *                Fail to read the entry
638     *
639     * @supported.api
640     */
641    public AttrSet read(java.security.Principal principal, Guid guid)
642            throws EntryNotFoundException, UMSException {
643        return read(principal, guid, null);
644    }
645
646    /**
647     * Reads an ldap entry.
648     * 
649     * @param principal Authentication Principal.
650     * @param guid Globally unique identifier for the entry.
651     * @param attrNames Attributes to read.
652     * @return an attribute set representing the entry in LDAP.
653     * @exception EntryNotFoundException if the entry is not found.
654     * @exception UMSException if fail to read the entry.
655     *
656     * @supported.api
657     */
658    public AttrSet read(
659        java.security.Principal principal,
660        Guid guid,
661        String attrNames[]
662    ) throws EntryNotFoundException, UMSException {
663        String id = guid.getDn();
664        LDAPEntry entry = null;
665        LDAPSearchRequest request = LDAPRequestParser.parseReadRequest(id,
666            attrNames);
667
668        try {
669            entry = readLDAPEntry(principal, request);
670        } catch (LDAPException e) {
671            if (debug.warningEnabled()) {
672                debug.warning("Exception in DataLayer.read for DN: " + id);
673                debug.warning("LDAPException: " + e);
674            }
675            int errorCode = e.getLDAPResultCode();
676            String[] args = { id };
677            if (errorCode == LDAPException.NO_SUCH_OBJECT) {
678                throw new EntryNotFoundException(i18n.getString(
679                        IUMSConstants.ENTRY_NOT_FOUND, args), e);
680            } else {
681                throw new UMSException(i18n.getString(
682                        IUMSConstants.UNABLE_TO_READ_ENTRY, args), e);
683            }
684        }
685
686        if (entry == null) {
687            throw new AccessRightsException(id);
688        }
689
690        LDAPAttributeSet ldapAttrSet = entry.getAttributeSet();
691        if (ldapAttrSet == null) {
692            String[] args = { id };
693            throw new EntryNotFoundException(i18n.getString(
694                    IUMSConstants.ENTRY_NOT_FOUND, args));
695        }
696
697        return new AttrSet(ldapAttrSet);
698    }
699
700    public void rename(java.security.Principal principal, Guid guid,
701            String newName, boolean deleteOldName)
702            throws AccessRightsException, EntryNotFoundException, UMSException {
703        LDAPConnection conn = null;
704        String id = guid.getDn();
705        int errorCode = 0;
706
707        try {
708            LDAPModifyRDNRequest request =
709                LDAPRequestParser.parseModifyRDNRequest(id, newName,
710                deleteOldName);
711            int retry = 0;
712            while (retry <= connNumRetry) {
713                if (debug.messageEnabled()) {
714                    debug.message("DataLayer.rename retry: " + retry);
715                }
716
717                try {
718                    conn = getConnection(principal);
719                    conn.rename(request);
720                    return;
721                } catch (LDAPException e) {
722                    errorCode = e.getLDAPResultCode();
723                    releaseConnection(conn, errorCode);
724                    conn = null;
725                    if (!retryErrorCodes.contains("" + e.getLDAPResultCode())
726                            || retry == connNumRetry) {
727                        throw e;
728                    }
729                    retry++;
730                    try {
731                        Thread.sleep(connRetryInterval);
732                    } catch (InterruptedException ex) {
733                    }
734                }
735            }
736        } catch (LDAPException e) {
737            if (debug.warningEnabled()) {
738                debug.warning("Exception in DataLayer.rename for DN: " + id, e);
739            }
740            errorCode = e.getLDAPResultCode();
741            switch (errorCode) {
742            case LDAPException.NO_SUCH_OBJECT:
743                throw new EntryNotFoundException(id, e);
744            case LDAPException.INSUFFICIENT_ACCESS_RIGHTS:
745                throw new AccessRightsException(id, e);
746            default:
747                throw new UMSException(id, e);
748            }
749        } finally {
750            if (conn != null) {
751                releaseConnection(conn);
752            }
753        }
754    }
755
756    /**
757     * Modifies an ldap entry.
758     * 
759     * @param principal Authentication Principal.
760     * @param guid globally unique identifier for the entry.
761     * @param modSet Set of modifications for the entry.
762     * @exception AccessRightsException if insufficient access
763     * @exception EntryNotFoundException if the entry is not found.
764     * @exception UMSException if failure
765     *
766     * @supported.api
767     */
768    public void modify(
769        java.security.Principal principal,
770        Guid guid,
771        ModSet modSet
772    ) throws AccessRightsException, EntryNotFoundException, UMSException {
773        LDAPConnection conn = null;
774        String id = guid.getDn();
775        int errorCode = 0;
776
777        try {
778            LDAPModifyRequest request = LDAPRequestParser.parseModifyRequest(
779                id, modSet);
780            int retry = 0;
781            while (retry <= connNumRetry) {
782                if (debug.messageEnabled()) {
783                    debug.message("DataLayer.modify retry: " + retry);
784                }
785
786                try {
787                    conn = getConnection(principal);
788                    conn.modify(request);
789                    return;
790                } catch (LDAPException e) {
791                    errorCode = e.getLDAPResultCode();
792                    releaseConnection(conn, errorCode);
793                    conn = null;
794                    if (!retryErrorCodes.contains("" + e.getLDAPResultCode())
795                            || retry == connNumRetry) {
796                        throw e;
797                    }
798                    retry++;
799                    try {
800                        Thread.sleep(connRetryInterval);
801                    } catch (InterruptedException ex) {
802                    }
803                }
804            }
805        } catch (LDAPException e) {
806            if (debug.warningEnabled()) {
807                debug.warning("Exception in DataLayer.modify for DN: " + id, e);
808            }
809            errorCode = e.getLDAPResultCode();
810            switch (errorCode) {
811            case LDAPException.NO_SUCH_OBJECT:
812                throw new EntryNotFoundException(id, e);
813            case LDAPException.INSUFFICIENT_ACCESS_RIGHTS:
814                throw new AccessRightsException(id, e);
815            default:
816                throw new UMSException(id, e);
817            }
818        } finally {
819            if (conn != null) {
820                releaseConnection(conn);
821            }
822        }
823    }
824
825    /**
826     * Changes user password.
827     * 
828     * @param guid globally unique identifier for the entry.
829     * @param attrName password attribute name
830     * @param oldPassword old password
831     * @param newPassword new password
832     * @exception AccessRightsException if insufficient access
833     * @exception EntryNotFoundException if the entry is not found.
834     * @exception UMSException if failure
835     *
836     * @supported.api
837     */
838    public void changePassword(Guid guid, String attrName, String oldPassword,
839        String newPassword)
840        throws AccessRightsException, EntryNotFoundException, UMSException {
841
842        ModSet modSet = new ModSet();
843        modSet.add(LDAPModification.REPLACE,
844            new LDAPAttribute(attrName, newPassword));
845
846        String id = guid.getDn();
847
848        LDAPConnection ldc = null;
849        int resultCode = 0;
850        try {
851            DSConfigMgr dsCfg = DSConfigMgr.getDSConfigMgr();
852            String hostAndPort = dsCfg.getHostName("default");
853
854            ldc = new LDAPConnection();
855            ldc.connect(hostAndPort, 389, id, oldPassword);
856
857            ldc.modify(id, modSet);
858        } catch (LDAPException ldex) {
859            if (debug.warningEnabled()) {
860                debug.warning("DataLayer.changePassword:", ldex);
861            }
862            int errorCode = ldex.getLDAPResultCode();
863            switch (errorCode) {
864            case LDAPException.NO_SUCH_OBJECT:
865                throw new EntryNotFoundException(id, ldex);
866            case LDAPException.INSUFFICIENT_ACCESS_RIGHTS:
867                throw new AccessRightsException(id, ldex);
868            default:
869                throw new UMSException(id, ldex);
870            }
871        } catch (LDAPServiceException ex) {
872            debug.error("DataLayer.changePassword:", ex);
873            throw new UMSException(id, ex);
874        } finally {
875            if (ldc != null) {
876                try {
877                    ldc.disconnect();
878                } catch (LDAPException lde) {
879                }
880            }
881        }
882    }
883
884    /**
885     * Adds value for an attribute and saves the change in the database.
886     * 
887     * @param principal Authenticated Principal.
888     * @param guid ID of the entry to which to add the attribute value.
889     * @param name name of the attribute to which value is being added.
890     * @param value Value to be added to the attribute.
891     * @throws UMSException if there is any error while adding the value
892     *
893     * @supported.api
894     */
895    public void addAttributeValue(
896        java.security.Principal principal,
897        Guid guid,
898        String name,
899        String value
900    ) throws UMSException {
901        ModSet modSet = new ModSet();
902        modSet.add(LDAPModification.ADD, new LDAPAttribute(name, value));
903
904        // Delegate to the other modify() method.
905        modify(principal, guid, modSet);
906    }
907
908    /**
909     * Removes value for an attribute and saves the change in the database.
910     * 
911     * @param principal Authenticated Principal.
912     * @param guid the id of the entry from which to remove the attribute value.
913     * @param name Name of the attribute from which value is being removed
914     * @param value Value to be removed from the attribute.
915     * @throws UMSException if there is any error while removing the value.
916     *
917     * @supported.api
918     */
919    public void removeAttributeValue(java.security.Principal principal,
920            Guid guid, String name, String value) throws UMSException {
921        ModSet modSet = new ModSet();
922        modSet.add(LDAPModification.DELETE, new LDAPAttribute(name, value));
923
924        // Delegate to the other modify() method.
925        modify(principal, guid, modSet);
926    }
927
928    /**
929     * retrive LDAPConnection for search.
930     */
931    private LDAPConnection getSearchConnection(
932            java.security.Principal principal, SearchControl searchControl) {
933        LDAPConnection conn = getConnection(principal);
934
935        if (searchControl != null) {
936            LDAPSearchConstraints constraints;
937            int[] vlvRange = searchControl.getVLVRange();
938            SortKey[] sortKeys = searchControl.getSortKeys();
939            LDAPSortKey[] ldapSortKeys;
940            ArrayList ctrls = new ArrayList(); // will hold all server controls
941
942            if (sortKeys != null) {
943                ldapSortKeys = new LDAPSortKey[sortKeys.length];
944                for (int i = 0; i < ldapSortKeys.length; i++) {
945                    ldapSortKeys[i] = new LDAPSortKey(
946                            sortKeys[i].attributeName, sortKeys[i].reverse);
947                }
948
949                ctrls.add(new LDAPSortControl(ldapSortKeys, false));
950
951                if (vlvRange != null) {
952                    if (searchControl.getVLVJumpTo() == null) {
953                        ctrls.add(new LDAPVirtualListControl(vlvRange[0],
954                                vlvRange[1], vlvRange[2], 0));
955                    } else {
956                        ctrls.add(new LDAPVirtualListControl(searchControl
957                                .getVLVJumpTo(), vlvRange[1], vlvRange[2]));
958                    }
959                }
960            }
961            
962            constraints = conn.getSearchConstraints();
963            LDAPControl[] existingCtrls = constraints.getServerControls();
964            ctrls.addAll(Arrays.asList(existingCtrls));
965
966            // This should be 0 if intermediate results are not needed,
967            // and 1 if results are to be processed as they come in.
968            // (By default, this is 1.)
969            constraints.setBatchSize(1);
970            constraints.setMaxResults(searchControl.getMaxResults());
971            constraints.setServerTimeLimit(searchControl.getTimeOut());
972            if (sortKeys != null) {
973                constraints.setServerControls((LDAPControl[]) ctrls
974                        .toArray(new LDAPControl[0]));
975            }
976
977            searchControl.set("constraints", constraints);
978        }
979
980        return conn;
981    }
982
983    /**
984     * Performs synchronous search based on specified ldap filter. This is low
985     * level API which assumes caller knows how to construct a data store filer.
986     * 
987     * @param principal Authenticated Principal.
988     * @param guid Unique identifier for the entry.
989     * @param scope Scope can be either <code>SCOPE_ONE</code>,
990     *        <code>SCOPE_SUB</code> or <code>SCOPE_BASE</code>.
991     * @param searchFilter Search filter for this search.
992     * @param attrNames Attribute name for retrieving.
993     * @param attrOnly if true, returns the names but not the values of the
994     *        attributes found.
995     * @param searchControl Search Control.
996     * @exception UMSException if failure.
997     * @exception InvalidSearchFilterException if failure
998     *
999     * @supported.api
1000     */
1001    public SearchResults search(
1002        java.security.Principal principal,
1003        Guid guid,
1004        int scope,
1005        String searchFilter,
1006        String attrNames[],
1007        boolean attrOnly,
1008        SearchControl searchControl
1009    ) throws InvalidSearchFilterException, UMSException {
1010        LDAPConnection conn = null;
1011        String id = guid.getDn();
1012
1013        // always add "objectclass" to attributes to get, to find the right java
1014        // class
1015        String[] attrNames1 = null;
1016        if (attrNames != null) {
1017            attrNames1 = new String[attrNames.length + 1];
1018            System.arraycopy(attrNames, 0, attrNames1, 0, attrNames.length);
1019            attrNames1[attrNames1.length - 1] = "objectclass";
1020        } else {
1021            attrNames1 = new String[] { "objectclass" };
1022        }
1023
1024        LDAPSearchResults ldapResults = null;
1025
1026        // if searchFilter is null, search for everything under the base
1027        if (searchFilter == null) {
1028            searchFilter = "(objectclass=*)";
1029        }
1030        int errorCode = 0;
1031
1032        try {
1033            conn = getSearchConnection(principal, searchControl);
1034            // call readLDAPEntry() only in replica case, save one LDAP search
1035            // assume replica case when replicaRetryNum is not 0
1036            if (replicaRetryNum != 0) {
1037                readLDAPEntry(conn, id, null);
1038            }
1039
1040            int retry = 0;
1041            while (retry <= connNumRetry) {
1042                if (debug.messageEnabled()) {
1043                    debug.message("DataLayer.search retry: " + retry);
1044                }
1045
1046                try {
1047                    if (searchControl == null) {
1048                        ldapResults = conn.search(id, scope, searchFilter,
1049                                attrNames1, attrOnly);
1050                    } else {
1051                        if (searchControl.isGetAllReturnAttributesEnabled()) {
1052                            /*
1053                             * The array {"*"} is used, because LDAPv3 defines
1054                             * "*" as a special string indicating all
1055                             * attributes. This gets all the attributes.
1056                             */
1057
1058                            attrNames1 = new String[] { "*" };
1059                        }
1060
1061                        ldapResults = conn.search(id, scope, searchFilter,
1062                                attrNames1, attrOnly,
1063                                (LDAPSearchConstraints) searchControl
1064                                        .get("constraints"));
1065                    }
1066                    break;
1067                } catch (LDAPException e) {
1068                    errorCode = e.getLDAPResultCode();
1069                    if (!retryErrorCodes.contains("" + e.getLDAPResultCode())
1070                            || retry == connNumRetry) {
1071                        throw e;
1072                    }
1073                    retry++;
1074                    try {
1075                        Thread.sleep(connRetryInterval);
1076                    } catch (InterruptedException ex) {
1077                    }
1078                }
1079            }
1080
1081            // TODO: need review and see if conn is recorded properly for
1082            // subsequent use
1083            //
1084            SearchResults result = new SearchResults(ldapResults, conn, this);
1085            result.set(SearchResults.BASE_ID, id);
1086            result.set(SearchResults.SEARCH_FILTER, searchFilter);
1087            result.set(SearchResults.SEARCH_SCOPE, new Integer(scope));
1088
1089            if ((searchControl != null)
1090                    && (searchControl.contains(SearchControl.KeyVlvRange) 
1091                       || searchControl.contains(SearchControl.KeyVlvJumpTo))) {
1092                result.set(SearchResults.EXPECT_VLV_RESPONSE, Boolean.TRUE);
1093
1094            }
1095
1096            if (searchControl != null
1097                    && searchControl.contains(SearchControl.KeySortKeys)) {
1098                SortKey[] sortKeys = searchControl.getSortKeys();
1099                if (sortKeys != null && sortKeys.length > 0) {
1100                    result.set(SearchResults.SORT_KEYS, sortKeys);
1101                }
1102            }
1103
1104            return result;
1105
1106        } catch (LDAPException e) {
1107            errorCode = e.getLDAPResultCode();
1108            releaseConnection(conn, errorCode);
1109            if (debug.warningEnabled()) {
1110                debug.warning("Exception in DataLayer.search: ", e);
1111            }
1112            String msg = i18n.getString(IUMSConstants.SEARCH_FAILED);
1113            switch (errorCode) {
1114            case LDAPException.TIME_LIMIT_EXCEEDED: {
1115                int timeLimit = searchControl != null ? searchControl
1116                        .getTimeOut() : 0;
1117                throw new TimeLimitExceededException(String.valueOf(timeLimit),
1118                        e);
1119            }
1120            case LDAPException.SIZE_LIMIT_EXCEEDED: {
1121                int sizeLimit = searchControl != null ? searchControl
1122                        .getMaxResults() : 0;
1123                throw new SizeLimitExceededException(String.valueOf(sizeLimit),
1124                        e);
1125            }
1126            case LDAPException.PARAM_ERROR:
1127            case LDAPException.PROTOCOL_ERROR:
1128                throw new InvalidSearchFilterException(searchFilter, e);
1129            default:
1130                throw new UMSException(msg, e);
1131            }
1132
1133        }
1134    }
1135
1136    /**
1137     * Perform synchronous search based on specified ldap filter. This is low
1138     * level API which assumes caller knows how to construct a data store filer.
1139     * 
1140     * @param principal Authenticated Principal.
1141     * @param guid Unique identifier for the entry
1142     * @param scope Scope can be either <code>SCOPE_ONE</code>,
1143     *        <code>SCOPE_SUB</code>, <code>SCOBE_BASE</code>
1144     * @param searchFilter Search filter for this search.
1145     * @param searchControl Search Control.
1146     * @exception UMSException if failure.
1147     * @exception InvalidSearchFilterException if failure.
1148     *
1149     * @supported.api
1150     */
1151    public SearchResults searchIDs(
1152        java.security.Principal principal,
1153        Guid guid,
1154        int scope,
1155        String searchFilter,
1156        SearchControl searchControl
1157    ) throws InvalidSearchFilterException, UMSException {
1158        // TODO: support LDAP referral
1159        String attrNames[] = { "objectclass" };
1160        return search(principal, guid, scope, searchFilter, attrNames, false,
1161                searchControl);
1162    }
1163
1164    /**
1165     * Fetches the schema from the LDAP directory server. Retrieve the entire
1166     * schema from the root of a Directory Server.
1167     * 
1168     * @return the schema in the LDAP directory server
1169     * @exception AccessRightsException
1170     *                insufficient access
1171     * @exception UMSException
1172     *                Fail to fetch the schema.
1173     *
1174     * @supported.api
1175     */
1176    public LDAPSchema getSchema(java.security.Principal principal)
1177            throws AccessRightsException, UMSException {
1178        LDAPConnection conn = null;
1179        LDAPSchema dirSchema = new LDAPSchema();
1180        int errorCode = 0;
1181
1182        try {
1183            LDAPSearchRequest request = LDAPRequestParser.parseReadRequest(
1184                "fake=fake");
1185            conn = getConnection(principal);            
1186            // disable the checking of attribute syntax quoting and the
1187            // read on ""
1188            conn.setProperty(DSConfigMgr.SCHEMA_BUG_PROPERTY,
1189                    DSConfigMgr.VAL_STANDARD);
1190            int retry = 0;
1191            while (retry <= connNumRetry) {
1192                if (debug.messageEnabled()) {
1193                    debug.message("DataLayer.getSchema retry: " + retry);
1194                }
1195
1196                try {
1197                    // after connection is down, fetchSchema will not try to
1198                    // reconnect. So use read to force it to reconnect
1199                    if (retry > 0) {
1200                        try {
1201                            conn.read(request);
1202                        } catch (Exception ex) {
1203                        }
1204                    }
1205
1206                    dirSchema.fetchSchema(conn, "cn=schema");
1207                    return dirSchema;
1208                } catch (LDAPException e) {
1209                    errorCode = e.getLDAPResultCode();
1210                    releaseConnection(conn, errorCode);
1211                    conn = null;
1212                    if (!retryErrorCodes.contains("" + e.getLDAPResultCode())
1213                            || retry == connNumRetry) {
1214                        throw e;
1215                    }
1216                    retry++;
1217                    try {
1218                        Thread.sleep(connRetryInterval);
1219                    } catch (InterruptedException ex) {
1220                    }
1221                }
1222            }
1223        } catch (LDAPException e) {
1224            debug.error("Exception in DataLayer.getSchema: ", e);
1225            errorCode = e.getLDAPResultCode();
1226            switch (errorCode) {
1227            case LDAPException.INSUFFICIENT_ACCESS_RIGHTS:
1228                throw new AccessRightsException(m_host, e);
1229            default:
1230                throw new UMSException(m_host, e);
1231            }
1232        } finally {
1233            if (conn != null) {
1234                releaseConnection(conn);
1235            }
1236        }
1237
1238        return dirSchema;
1239    }
1240
1241    /**
1242     * Adds schema element to the schema at the root DSE
1243     * 
1244     * @param schemaElement
1245     *            schema element to be added
1246     * @exception AccessRightsException
1247     *                insufficient access
1248     * @exception SchemaElementAlreadyExistsException
1249     *                if the element already exists
1250     * @exception UMSException
1251     *                Fail to add schema element.
1252     *
1253     * @supported.api
1254     */
1255    public void addSchema(java.security.Principal principal,
1256            LDAPSchemaElement schemaElement) throws AccessRightsException,
1257            SchemaElementAlreadyExistsException, UMSException {
1258        LDAPConnection conn = null;
1259        try {
1260            conn = getConnection(principal);
1261            // disable the checking of attribute syntax quoting and the
1262            // read on ""
1263            conn.setProperty("com.sun.identity.shared.ldap.schema.quoting",
1264                "standard");
1265            schemaElement.add(conn, "cn=schema");
1266        } catch (LDAPException e) {
1267            int errorCode = e.getLDAPResultCode();
1268            releaseConnection(conn, errorCode);
1269            conn = null;
1270            debug.error("Exception in DataLayer.addSchema: ", e);
1271            switch (errorCode) {
1272            case LDAPException.ATTRIBUTE_OR_VALUE_EXISTS:
1273                throw new SchemaElementAlreadyExistsException(schemaElement
1274                        .getName(), e);
1275            case LDAPException.INSUFFICIENT_ACCESS_RIGHTS:
1276                throw new AccessRightsException(schemaElement.getName(), e);
1277            default:
1278                throw new UMSException(schemaElement.getName(), e);
1279            }
1280        } finally {
1281            if (conn != null) {
1282                releaseConnection(conn);
1283            }
1284        }
1285    }
1286
1287    /**
1288     * Removes schema element from the schema
1289     * 
1290     * @param schemaElement
1291     *            schema element to be removed
1292     * @exception AccessRightsException
1293     *                insufficient access
1294     * @exception UMSException
1295     *                Fail to remove schema element.
1296     *
1297     * @supported.api
1298     */
1299    public void removeSchema(java.security.Principal principal,
1300            LDAPSchemaElement schemaElement) throws AccessRightsException,
1301            UMSException {
1302        LDAPConnection conn = null;
1303
1304        try {
1305            conn = getConnection(principal);
1306            // disable the checking of attribute syntax quoting and the
1307            // read on ""
1308            conn.setProperty("com.sun.identity.shared.ldap.schema.quoting",
1309                "standard");
1310            schemaElement.remove(conn, "cn=schema");
1311
1312        } catch (LDAPException e) {
1313            int errorCode = e.getLDAPResultCode();
1314            releaseConnection(conn, errorCode);
1315            conn = null;
1316            debug.error("Exception in DataLayer.removeSchema:", e);
1317            switch (errorCode) {
1318            case LDAPException.INSUFFICIENT_ACCESS_RIGHTS:
1319                throw new AccessRightsException(schemaElement.getName(), e);
1320            default:
1321                throw new UMSException(schemaElement.getName(), e);
1322            }
1323        } finally {
1324            if (conn != null) {
1325                releaseConnection(conn);
1326            }
1327        }
1328    }
1329
1330    private void initReplicaProperties() {
1331        String retries = SystemProperties
1332                .get("com.iplanet.am.replica.num.retries");
1333        if (retries != null) {
1334            try {
1335                replicaRetryNum = Integer.parseInt(retries);
1336                if (replicaRetryNum < 0) {
1337                    replicaRetryNum = 0;
1338                    debug.warning("Invalid value for replica retry num, " +
1339                            "set to 0");
1340                }
1341
1342            } catch (NumberFormatException e) {
1343                debug.warning("Invalid value for replica retry num");
1344            }
1345        }
1346
1347        String interval = SystemProperties
1348                .get("com.iplanet.am.replica.delay.between.retries");
1349        if (interval != null) {
1350            try {
1351                replicaRetryInterval = Long.parseLong(interval);
1352                if (replicaRetryInterval < 0) {
1353                    replicaRetryInterval = 0;
1354                    debug.warning("Invalid value for replica interval, " +
1355                            "set to 0");
1356                }
1357
1358            } catch (NumberFormatException e) {
1359                debug.warning("Invalid value for replica interval");
1360            }
1361        }
1362    }
1363
1364    public LDAPEntry readLDAPEntry(LDAPConnection ld, String dn,
1365            String[] attrnames) throws LDAPException {
1366
1367        LDAPException ldapEx = null;
1368        int retry = 0;
1369        int connRetry = 0;
1370        while (retry <= replicaRetryNum && connRetry <= connNumRetry) {
1371            if (debug.messageEnabled()) {
1372                debug.message("DataLayer.readLDAPEntry: connRetry: "
1373                        + connRetry);
1374                debug.message("DataLayer.readLDAPEntry: retry: " + retry);
1375            }
1376            try {
1377                if (attrnames == null) {
1378                    return ld.read(dn);
1379                } else {
1380                    return ld.read(dn, attrnames);
1381                }
1382            } catch (LDAPException e) {
1383                int errorCode = e.getLDAPResultCode();
1384                if (errorCode == LDAPException.NO_SUCH_OBJECT) {
1385                    if (debug.messageEnabled()) {
1386                        debug.message("Replica: entry not found: " + dn
1387                                + " retry: " + retry);
1388                    }
1389                    if (retry == replicaRetryNum) {
1390                        ldapEx = e;
1391                    } else {
1392                        try {
1393                            Thread.sleep(replicaRetryInterval);
1394                        } catch (Exception ex) {
1395                        }
1396                    }
1397                    retry++;
1398                } else if (retryErrorCodes.contains("" + errorCode)) {
1399                    if (connRetry == connNumRetry) {
1400                        ldapEx = e;
1401                    } else {
1402                        try {
1403                            Thread.sleep(connRetryInterval);
1404                        } catch (Exception ex) {
1405                        }
1406                    }
1407                    connRetry++;
1408                } else {
1409                    throw e;
1410                }
1411            }
1412        }
1413
1414        throw ldapEx;
1415    }
1416
1417    public LDAPEntry readLDAPEntry(java.security.Principal principal,
1418        LDAPSearchRequest request) throws LDAPException {
1419
1420        LDAPException ldapEx = null;
1421        int retry = 0;
1422        int connRetry = 0;
1423        LDAPConnection ld = null;
1424        while (retry <= replicaRetryNum && connRetry <= connNumRetry) {
1425            if (debug.messageEnabled()) {
1426                debug.message("DataLayer.readLDAPEntry: connRetry: "
1427                        + connRetry);
1428                debug.message("DataLayer.readLDAPEntry: retry: " + retry);
1429            }
1430            try {
1431                ld = getConnection(principal);
1432                return ld.read(request);
1433            } catch (LDAPException e) {
1434                int errorCode = e.getLDAPResultCode();
1435                releaseConnection(ld, errorCode);
1436                ld = null;
1437                if (errorCode == LDAPException.NO_SUCH_OBJECT) {
1438                    if (debug.messageEnabled()) {
1439                        debug.message("Replica: entry not found: " +
1440                            request.getBaseDN() + " retry: " + retry);
1441                    }
1442                    if (retry == replicaRetryNum) {
1443                        ldapEx = e;
1444                    } else {
1445                        try {
1446                            Thread.sleep(replicaRetryInterval);
1447                        } catch (Exception ex) {
1448                        }
1449                    }
1450                    retry++;
1451                } else if (retryErrorCodes.contains("" + errorCode)) {
1452                    if (connRetry == connNumRetry) {
1453                        ldapEx = e;
1454                    } else {
1455                        try {
1456                            Thread.sleep(connRetryInterval);
1457                        } catch (Exception ex) {
1458                        }
1459                    }
1460                    connRetry++;
1461                } else {
1462                    throw e;
1463                }
1464            } finally {
1465                if (ld != null) {
1466                    releaseConnection(ld);
1467                }
1468            }
1469        }
1470
1471        throw ldapEx;
1472    }
1473
1474
1475    /**
1476     * Initialize the pool shared by all DataLayer object(s).
1477     * 
1478     * @param host
1479     *            ldaphost to init the pool from
1480     * @param port
1481     *            ldapport to init the pool from
1482     */
1483    private synchronized void initLdapPool() throws UMSException {
1484        // Don't do anything if pool is already initialized
1485        if (_ldapPool != null)
1486            return;
1487
1488        /*
1489         * Initialize the pool with minimum and maximum connections settings
1490         * retrieved from configuration
1491         */
1492        ServerInstance svrCfg = null;
1493        String hostName = null;
1494        HashMap connOptions = new HashMap();
1495
1496        try {
1497            DSConfigMgr dsCfg = DSConfigMgr.getDSConfigMgr();
1498            hostName = dsCfg.getHostName("default");
1499
1500            _trialConn = dsCfg.getNewProxyConnection();
1501
1502            svrCfg = dsCfg.getServerInstance(LDAPUser.Type.AUTH_PROXY);
1503        } catch (LDAPServiceException ex) {
1504            debug.error("Error initializing connection pool "
1505                        + ex.getMessage());
1506        }
1507        
1508        // Check if svrCfg was successfully obtained
1509        if ((svrCfg == null) || (_trialConn == null)) {
1510            debug.error("Error getting server config.");
1511            // throw exception
1512            String args[] = new String[1];
1513            args[0] = (hostName == null) ? "default" : hostName;
1514            throw new UMSException(i18n.getString(
1515                IUMSConstants.NEW_INSTANCE_FAILED, args));
1516        }
1517
1518        int poolMin = svrCfg.getMinConnections();
1519        int poolMax = svrCfg.getMaxConnections();
1520        int maxBackLog = svrCfg.getIntValue(LDAP_MAXBACKLOG, MAX_BACKLOG);
1521        m_releaseConnectionBeforeSearchCompletes = svrCfg.getBooleanValue(
1522                LDAP_RELEASECONNBEFORESEARCH, false);
1523        boolean referrals = svrCfg.getBooleanValue(LDAP_REFERRAL, true);
1524        String connDN = svrCfg.getAuthID();
1525        String connPWD = svrCfg.getPasswd();
1526
1527        if (debug.messageEnabled()) {
1528            debug.message("Creating ldap connection pool with :");
1529            debug.message("poolMin : " + poolMin);
1530            debug.message("poolMax : " + poolMax);
1531            debug.message("maxBackLog : " + maxBackLog);
1532        }
1533
1534        try {
1535            // establish one good connection before the pool
1536            // _trialConn = new LDAPConnection();
1537
1538            _trialConn.setOption(LDAPConnection.MAXBACKLOG, new Integer(
1539                    maxBackLog));
1540            _trialConn.setOption(LDAPConnection.REFERRALS, Boolean.valueOf(
1541                    referrals));
1542
1543            /*
1544             * Default rebind method is to provide the same authentication
1545             * in the rebind to the server being referred.
1546             */
1547            LDAPBind defaultBinder = new LDAPBind() {
1548                public void bind(LDAPConnection ld) throws LDAPException {
1549                    /*
1550                     * There is possibly a bug in the ldapjdk that the passed in
1551                     * ld is not carrying the original authentication dn and pwd
1552                     * Hence, we have to kludge here using the one connection
1553                     * that we know
1554                     * about: the connection that we use to initialize the
1555                     * connection
1556                     * pool.
1557                     * TODO: need to investigate
1558                     */
1559                    String dn = _trialConn.getAuthenticationDN();
1560                    String pwd = _trialConn.getAuthenticationPassword();
1561                    String newhost = ld.getHost();
1562                    int newport = ld.getPort();
1563                    ld.connect(3, newhost, newport, dn, pwd);
1564                }
1565            };
1566            _trialConn.setOption(LDAPConnection.BIND, defaultBinder);
1567
1568            // _trialConn.connect(3, m_host, m_port, m_proxyUser,
1569            // m_proxyPassword);
1570
1571            // remember the original search constraints
1572            _defaultSearchConstraints = _trialConn.getSearchConstraints();
1573
1574            // Construct the pool by cloning the successful connection
1575            // Set the default options too for failover and fallback features.
1576
1577            connOptions.put("maxbacklog", new Integer(maxBackLog));
1578            connOptions.put("referrals", Boolean.valueOf(referrals));
1579            connOptions.put("searchconstraints", _defaultSearchConstraints);
1580
1581            ShutdownManager shutdownMan = ShutdownManager.getInstance();
1582            if (shutdownMan.acquireValidLock()) {
1583                try {
1584                    _ldapPool = new LDAPConnectionPool("DataLayer", poolMin,
1585                        poolMax, hostName, 389, connDN, connPWD, _trialConn,
1586                        connOptions);
1587                    shutdownMan.addShutdownListener(
1588                        new ShutdownListener() {
1589                            public void shutdown() {
1590                                if (_ldapPool != null) {
1591                                    _ldapPool.destroy();
1592                                }
1593                            }
1594                        }
1595                    );
1596                } finally {
1597                    shutdownMan.releaseLockAndNotify();
1598                }
1599            }
1600
1601        } catch (LDAPException e) {
1602            debug.error("Exception in DataLayer.initLdapPool:", e);
1603        }
1604    }
1605
1606    public static int getConnNumRetry() {
1607        return connNumRetry;
1608    }
1609
1610    public static int getConnRetryInterval() {
1611        return connRetryInterval;
1612    }
1613
1614    public static HashSet getRetryErrorCodes() {
1615        return retryErrorCodes;
1616    }
1617    
1618    private static void initializeEventService() {
1619        // Initialize event service. This is to make sure that EventService
1620        // thread is started. The other place where it is also tried to start
1621        // is: com.iplanet.am.sdk.ldap.AMEventManager which is
1622        // initialized in com.iplanet.am.sdk.ldap.DirectoryManager
1623        if (!EventService.isThreadStarted()) {
1624            // Use a separate thread to start the EventService thread.
1625            // This will prevent deadlocks associated in the system because
1626            // of EventService related dependencies.
1627            InitEventServiceThread th = new InitEventServiceThread();
1628            Thread initEventServiceThread = new Thread(th,
1629                "InitEventServiceThread");
1630            initEventServiceThread.setDaemon(true);
1631            initEventServiceThread.start();
1632        }
1633    }
1634
1635    private static class InitEventServiceThread implements Runnable {
1636        public void run() {
1637            debug.message("InitEventServiceThread:initializeEventService() - "
1638                + "EventService thread getting  initialized ");
1639            try {
1640                EventService es = EventService.getEventService();
1641                if (!EventService.isThreadStarted()) {
1642                    es.resetAllSearches(false);
1643                }
1644            } catch (Exception e) {
1645                // An Error occurred while intializing EventService
1646                debug.error("InitEventServiceThread:run() Unable to "
1647                    + "start EventService!!", e);
1648            }
1649        }
1650    }    
1651
1652    static private LDAPConnectionPool _ldapPool = null;
1653
1654    static private LDAPConnection _trialConn = null;
1655
1656    static private LDAPSearchConstraints _defaultSearchConstraints = null;
1657
1658    static private DataLayer m_instance = null;
1659
1660    private String m_host = null;
1661
1662    private int m_port;
1663
1664    private String m_proxyUser = "";
1665
1666    private String m_proxyPassword = "";
1667
1668    private boolean m_releaseConnectionBeforeSearchCompletes = false;
1669
1670    private static final String[] EMPTY_STRING_ARRAY = new String[0];
1671
1672}