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




























































Copyright © 2010-2017, ForgeRock All Rights Reserved.