    001    package org.apache.turbine.services.security.ldap;
    002
    003    /*
    004     * Licensed to the Apache Software Foundation (ASF) under one
    005     * or more contributor license agreements.  See the NOTICE file
    006     * distributed with this work for additional information
    007     * regarding copyright ownership.  The ASF licenses this file
    008     * to you under the Apache License, Version 2.0 (the
    009     * "License"); you may not use this file except in compliance
    010     * with the License.  You may obtain a copy of the License at
    011     *
    012     *   http://www.apache.org/licenses/LICENSE-2.0
    013     *
    014     * Unless required by applicable law or agreed to in writing,
    015     * software distributed under the License is distributed on an
    016     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    017     * KIND, either express or implied.  See the License for the
    018     * specific language governing permissions and limitations
    019     * under the License.
    020     */
    021
    022    import java.io.ByteArrayOutputStream;
    023    import java.io.PrintWriter;
    024    import java.sql.Connection;
    025    import java.util.Hashtable;
    026
    027    import javax.naming.NamingException;
    028    import javax.naming.directory.Attribute;
    029    import javax.naming.directory.Attributes;
    030    import javax.naming.directory.BasicAttribute;
    031    import javax.naming.directory.BasicAttributes;
    032    import javax.servlet.http.HttpSessionBindingEvent;
    033
    034    import org.apache.commons.logging.Log;
    035    import org.apache.commons.logging.LogFactory;
    036    import org.apache.turbine.om.security.User;
    037    import org.apache.turbine.services.security.TurbineSecurity;
    038
    039    /**
    040     * LDAPUser implements User and provides access to a user who accesses the
    041     * system via LDAP.
    042     *
    043     * @author <a href="mailto:cberry@gluecode.com">Craig D. Berry</a>
    044     * @author <a href="mailto:tadewunmi@gluecode.com">Tracy M. Adewunmi</a>
    045     * @author <a href="mailto:lflournoy@gluecode.com">Leonard J. Flournoy </a>
    046     * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
    047     * @author <a href="mailto:hhernandez@itweb.com.mx">Humberto Hernandez</a>
    048     */
    049    public class LDAPUser implements User
    050    {
    051
    052        /** Logging */
    053        private static Log log = LogFactory.getLog(LDAPUser.class);
    054
    055        /* A few attributes common to a User. */
    056
    057        /** Date when the user was created */
    058        private java.util.Date createDate = null;
    059
    060        /** Date when the user was last accessed */
    061        private java.util.Date lastAccessDate = null;
    062
    063        /** timeout */
    064        private int timeout = 15;
    065
    066        /** This is data that will survive a servlet engine restart. */
    067        private Hashtable permStorage = null;
    068
    069        /** This is data that will not survive a servlet engine restart. */
    070        private Hashtable tempStorage = null;
    071
    072        /**
    073         * Constructor.
    074         * Create a new User and set the createDate.
    075         */
    076        public LDAPUser()
    077        {
    078            createDate = new java.util.Date();
    079            tempStorage = new Hashtable(10);
    080            permStorage = new Hashtable(10);
    081            setHasLoggedIn(Boolean.FALSE);
    082        }
    083
    084        /**
    085         * Populates the user with values obtained from the LDAP Service.
    086         * This method could be redefined in subclasses.
    087         * @param attribs The attributes obtained from LDAP.
    088         * @throws NamingException if there was an error with JNDI.
    089         */
    090        public void setLDAPAttributes(Attributes attribs)
    091                throws NamingException
    092        {
    093
    094            Attribute attr;
    095            String attrName;
    096
    097            // Set the User id.
    098            attrName = LDAPSecurityConstants.getUserIdAttribute();
    099            if (attrName != null)
    100            {
    101                attr = attribs.get(attrName);
    102                if (attr != null && attr.get() != null)
    103                {
    104                    try
    105                    {
    106                        //setPrimaryKey(attr.get().toString());
    107                    }
    108                    catch (Exception ex)
    109                    {
    110                        log.error("Exception caught:", ex);
    111                    }
    112                }
    113            }
    114
    115            // Set the Username.
    116            attrName = LDAPSecurityConstants.getNameAttribute();
    117            if (attrName != null)
    118            {
    119                attr = attribs.get(attrName);
    120                if (attr != null && attr.get() != null)
    121                {
    122                    setName(attr.get().toString());
    123                }
    124            }
    125            else
    126            {
    127                log.error("There is no LDAP attribute for the username.");
    128            }
    129
    130            // Set the Firstname.
    131            attrName = LDAPSecurityConstants.getFirstNameAttribute();
    132            if (attrName != null)
    133            {
    134                attr = attribs.get(attrName);
    135                if (attr != null && attr.get() != null)
    136                {
    137                    setFirstName(attr.get().toString());
    138                }
    139            }
    140
    141            // Set the Lastname.
    142            attrName = LDAPSecurityConstants.getLastNameAttribute();
    143            if (attrName != null)
    144            {
    145                attr = attribs.get(attrName);
    146                if (attr != null && attr.get() != null)
    147                {
    148                    setLastName(attr.get().toString());
    149                }
    150            }
    151
    152            // Set the E-Mail
    153            attrName = LDAPSecurityConstants.getEmailAttribute();
    154            if (attrName != null)
    155            {
    156                attr = attribs.get(attrName);
    157                if (attr != null && attr.get() != null)
    158                {
    159                    setEmail(attr.get().toString());
    160                }
    161            }
    162        }
    163
    164        /**
    165         * Get the JNDI Attributes used to store the user in LDAP.
    166         * This method could be redefined in a subclass.
    167         *
    168         * @throws NamingException if there is a JNDI error.
    169         * @return The JNDI attributes of the user.
    170         */
    171        public Attributes getLDAPAttributes()
    172                throws NamingException
    173        {
    174            Attributes attribs = new BasicAttributes();
    175            String attrName;
    176
    177            // Set the objectClass
    178            attrName = "objectClass";
    179            if (attrName != null)
    180            {
    181                Object value = "turbineUser";
    182
    183                if (value != null)
    184                {
    185                    Attribute attr = new BasicAttribute(attrName, value);
    186
    187                    attribs.put(attr);
    188                }
    189            }
    190
    191            // Set the User id.
    192            attrName = LDAPSecurityConstants.getUserIdAttribute();
    193            if (attrName != null)
    194            {
    195                Object value = this.getIdAsObj();
    196
    197                if (value != null)
    198                {
    199                    Attribute attr = new BasicAttribute(attrName, value);
    200
    201                    attribs.put(attr);
    202                }
    203            }
    204
    205            // Set the Username.
    206            attrName = LDAPSecurityConstants.getNameAttribute();
    207            if (attrName != null)
    208            {
    209                Object value = getName();
    210
    211                if (value != null)
    212                {
    213                    Attribute attr = new BasicAttribute(attrName, value);
    214
    215                    attribs.put(attr);
    216                }
    217            }
    218
    219            // Set the Firstname.
    220            attrName = LDAPSecurityConstants.getFirstNameAttribute();
    221            if (attrName != null)
    222            {
    223                Object value = getFirstName();
    224
    225                if (value != null)
    226                {
    227                    Attribute attr = new BasicAttribute(attrName, value);
    228
    229                    attribs.put(attr);
    230                }
    231            }
    232
    233            // Set the Lastname.
    234            attrName = LDAPSecurityConstants.getLastNameAttribute();
    235            if (attrName != null)
    236            {
    237                Object value = getLastName();
    238
    239                if (value != null)
    240                {
    241                    Attribute attr = new BasicAttribute(attrName, value);
    242
    243                    attribs.put(attr);
    244                }
    245            }
    246
    247            // Set the E-Mail.
    248            attrName = LDAPSecurityConstants.getEmailAttribute();
    249            if (attrName != null)
    250            {
    251                Object value = getEmail();
    252
    253                if (value != null)
    254                {
    255                    Attribute attr = new BasicAttribute(attrName, value);
    256
    257                    attribs.put(attr);
    258                }
    259            }
    260
    261            // Set the Password
    262            attrName = LDAPSecurityConstants.getPasswordAttribute();
    263            if (attrName != null)
    264            {
    265                Object value = getPassword();
    266
    267                if (value != null)
    268                {
    269                    Attribute attr = new BasicAttribute(attrName, value);
    270
    271                    attribs.put(attr);
    272                }
    273            }
    274
    275            return attribs;
    276        }
    277
    278        /**
    279         * Gets the distinguished name (DN) of the User.
    280         * This method could be redefined in a subclass.
    281         * @return The Distinguished Name of the user.
    282         */
    283        public String getDN()
    284        {
    285            String filterAttribute = LDAPSecurityConstants.getNameAttribute();
    286            String userBaseSearch = LDAPSecurityConstants.getBaseSearch();
    287            String userName = getName();
    288
    289            String dn = filterAttribute + "=" + userName + "," + userBaseSearch;
    290
    291            return dn;
    292        }
    293
    294        /**
    295         * Gets the access counter for a user during a session.
    296         *
    297         * @return The access counter for the user for the session.
    298         */
    299        public int getAccessCounterForSession()
    300        {
    301            try
    302            {
    303                return ((Integer) getTemp(User.SESSION_ACCESS_COUNTER)).intValue();
    304            }
    305            catch (Exception e)
    306            {
    307                return 0;
    308            }
    309        }
    310
    311        /**
    312         * Gets the access counter for a user from perm storage.
    313         *
    314         * @return The access counter for the user.
    315         */
    316        public int getAccessCounter()
    317        {
    318            try
    319            {
    320                return ((Integer) getPerm(User.ACCESS_COUNTER)).intValue();
    321            }
    322            catch (Exception e)
    323            {
    324                return 0;
    325            }
    326        }
    327
    328        /**
    329         * Gets the create date for this User.  This is the time at which
    330         * the user object was created.
    331         *
    332         * @return A Java Date with the date of creation for the user.
    333         */
    334        public java.util.Date getCreateDate()
    335        {
    336            return createDate;
    337        }
    338
    339        /**
    340         * Returns the value of Confirmed variable
    341         * @return the confirm value.
    342         */
    343        public String getConfirmed()
    344        {
    345            String tmp = null;
    346
    347            tmp = (String) getPerm(User.CONFIRM_VALUE);
    348            if (tmp != null && tmp.length() == 0)
    349            {
    350                tmp = null;
    351            }
    352            return tmp;
    353        }
    354
    355        /**
    356         * Returns the Email for this user.  If this is defined, then
    357         * the user is considered logged in.
    358         *
    359         * @return A String with the user's Email.
    360         */
    361        public String getEmail()
    362        {
    363            String tmp = null;
    364
    365            tmp = (String) getPerm(User.EMAIL);
    366            if (tmp != null && tmp.length() == 0)
    367            {
    368                tmp = null;
    369            }
    370            return tmp;
    371        }
    372
    373        /**
    374         * Gets the last access date for this User.  This is the last time
    375         * that the user object was referenced.
    376         *
    377         * @return A Java Date with the last access date for the user.
    378         */
    379        public java.util.Date getLastAccessDate()
    380        {
    381            if (lastAccessDate == null)
    382            {
    383                setLastAccessDate();
    384            }
    385            return lastAccessDate;
    386        }
    387
    388        /**
    389         * Get last login date/time for this user.
    390         *
    391         * @return A Java Date with the last login date for the user.
    392         */
    393        public java.util.Date getLastLogin()
    394        {
    395            return (java.util.Date) getPerm(User.LAST_LOGIN);
    396        }
    397
    398        /**
    399         * Get password for this user.
    400         *
    401         * @return A String with the password for the user.
    402         */
    403        public String getPassword()
    404        {
    405            return (String) getPerm(User.PASSWORD);
    406        }
    407
    408        /**
    409         * Get an object from permanent storage.
    410         * @param name The object's name.
    411         * @return An Object with the given name.
    412         */
    413        public Object getPerm(String name)
    414        {
    415            return permStorage.get(name);
    416        }
    417
    418        /**
    419         * Get an object from permanent storage; return default if value
    420         * is null.
    421         *
    422         * @param name The object's name.
    423         * @param def A default value to return.
    424         * @return An Object with the given name.
    425         */
    426        public Object getPerm(String name, Object def)
    427        {
    428            try
    429            {
    430                Object val = permStorage.get(name);
    431
    432                if (val == null)
    433                {
    434                    return def;
    435                }
    436                return val;
    437            }
    438            catch (Exception e)
    439            {
    440                return def;
    441            }
    442        }
    443
    444        /**
    445         * This should only be used in the case where we want to save the
    446         * data to the database.
    447         *
    448         * @return A Hashtable.
    449         */
    450        public Hashtable getPermStorage()
    451        {
    452            if (this.permStorage == null)
    453            {
    454                this.permStorage = new Hashtable();
    455            }
    456            return this.permStorage;
    457        }
    458
    459        /**
    460         * Get an object from temporary storage.
    461         *
    462         * @param name The object's name.
    463         * @return An Object with the given name.
    464         */
    465        public Object getTemp(String name)
    466        {
    467            return tempStorage.get(name);
    468        }
    469
    470        /**
    471         * Get an object from temporary storage; return default if value
    472         * is null.
    473         *
    474         * @param name The object's name.
    475         * @param def A default value to return.
    476         * @return An Object with the given name.
    477         */
    478        public Object getTemp(String name, Object def)
    479        {
    480            Object val;
    481
    482            try
    483            {
    484                val = tempStorage.get(name);
    485                if (val == null)
    486                {
    487                    val = def;
    488                }
    489            }
    490            catch (Exception e)
    491            {
    492                val = def;
    493            }
    494            return val;
    495        }
    496
    497        /**
    498         * A User object can have a variable Timeout, which is defined in
    499         * minutes.  If the user has been timed out, then the
    500         * hasLoggedIn() value will return false.
    501         *
    502         * @return An int specifying the timeout.
    503         */
    504        public int getTimeout()
    505        {
    506            return this.timeout;
    507        }
    508
    509
    510        /**
    511         * Returns the first name for this user.  If this is defined, then
    512         * the user is considered logged in.
    513         *
    514         * @return A String with the user's first name.
    515         */
    516        public String getFirstName()
    517        {
    518            String tmp = null;
    519
    520            tmp = (String) getPerm(User.FIRST_NAME);
    521            if (tmp != null && tmp.length() == 0)
    522            {
    523                tmp = null;
    524            }
    525            return tmp;
    526        }
    527
    528        /**
    529         * Returns the last name for this user.  If this is defined, then
    530         * the user is considered logged in.
    531         *
    532         * @return A String with the user's last name.
    533         */
    534        public String getLastName()
    535        {
    536            String tmp = null;
    537
    538            tmp = (String) getPerm(User.LAST_NAME);
    539            if (tmp != null && tmp.length() == 0)
    540            {
    541                tmp = null;
    542            }
    543            return tmp;
    544        }
    545
    546        /**
    547         * The user is considered logged in if they have not timed out.
    548         *
    549         * @return True if the user has logged in.
    550         */
    551        public boolean hasLoggedIn()
    552        {
    553            Boolean tmp = getHasLoggedIn();
    554
    555            if (tmp != null && tmp.booleanValue())
    556            {
    557                return true;
    558            }
    559            else
    560            {
    561                return false;
    562            }
    563        }
    564
    565        /**
    566         * This method reports whether or not the user has been confirmed
    567         * in the system by checking the <code>CONFIRM_VALUE</code>
    568         * column to see if it is equal to <code>CONFIRM_DATA</code>.
    569         *
    570         * @return True if the user has been confirmed.
    571         */
    572        public boolean isConfirmed()
    573        {
    574            return ((String) getTemp(CONFIRM_VALUE, "")).equals(CONFIRM_DATA);
    575        }
    576
    577        /**
    578         * Increments the permanent hit counter for the user.
    579         */
    580        public void incrementAccessCounter()
    581        {
    582            setAccessCounter(getAccessCounter() + 1);
    583        }
    584
    585        /**
    586         * Increments the session hit counter for the user.
    587         */
    588        public void incrementAccessCounterForSession()
    589        {
    590            setAccessCounterForSession(getAccessCounterForSession() + 1);
    591        }
    592
    593        /**
    594         * Remove an object from temporary storage and return the object.
    595         *
    596         * @param name The name of the object to remove.
    597         * @return An Object.
    598         */
    599        public Object removeTemp(String name)
    600        {
    601            return tempStorage.remove(name);
    602        }
    603
    604        /**
    605         * Sets the access counter for a user, saved in perm storage.
    606         *
    607         * @param cnt The new count.
    608         */
    609        public void setAccessCounter(int cnt)
    610        {
    611            setPerm(User.ACCESS_COUNTER, new Integer(cnt));
    612        }
    613
    614        /**
    615         * Sets the session access counter for a user, saved in temp
    616         * storage.
    617         *
    618         * @param cnt The new count.
    619         */
    620        public void setAccessCounterForSession(int cnt)
    621        {
    622            setTemp(User.SESSION_ACCESS_COUNTER, new Integer(cnt));
    623        }
    624
    625        /**
    626         * Set the users confirmed variable
    627         *
    628         * @param confirm The new confim value.
    629         */
    630        public void setConfirmed(String confirm)
    631        {
    632            getPerm(User.CONFIRM_VALUE, confirm);
    633        }
    634
    635        /**
    636         * Sets the last access date for this User. This is the last time
    637         * that the user object was referenced.
    638         */
    639        public void setLastAccessDate()
    640        {
    641            lastAccessDate = new java.util.Date();
    642        }
    643
    644        /**
    645         * Sets the create date for this User. This is the time at which
    646         * the user object was created.
    647         *
    648         * @param date The create date.
    649         */
    650        public void setCreateDate(java.util.Date date)
    651        {
    652            createDate = date;
    653        }
    654
    655        /**
    656         * Set the users Email
    657         *
    658         * @param email The new email.
    659         */
    660        public void setEmail(String email)
    661        {
    662            setPerm(User.EMAIL, email);
    663        }
    664
    665        /**
    666         * Set the users First Name
    667         *
    668         * @param fname The new firstname.
    669         */
    670        public void setFirstName(String fname)
    671        {
    672            setPerm(User.FIRST_NAME, fname);
    673        }
    674
    675        /**
    676         * Set last login date/time.
    677         *
    678         * @param date The last login date.
    679         */
    680        public void setLastLogin(java.util.Date date)
    681        {
    682            setPerm(User.LAST_LOGIN, date);
    683        }
    684
    685        /**
    686         * Set the users Last Name
    687         * Sets the last name for this user.
    688         *
    689         * @param lname The new lastname.
    690         */
    691        public void setLastName(String lname)
    692        {
    693            setPerm(User.LAST_NAME, lname);
    694        }
    695
    696        /**
    697         * Set password.
    698         *
    699         * @param password The new password.
    700         */
    701        public void setPassword(String password)
    702        {
    703            setPerm(User.PASSWORD, password);
    704        }
    705
    706        /**
    707         * Put an object into permanent storage.
    708         *
    709         * @param name The object's name.
    710         * @param value The object.
    711         */
    712        public void setPerm(String name, Object value)
    713        {
    714            permStorage.put(name, value);
    715        }
    716
    717        /**
    718         * This should only be used in the case where we want to save the
    719         * data to the database.
    720         *
    721         * @param stuff A Hashtable.
    722         */
    723        public void setPermStorage(Hashtable stuff)
    724        {
    725            this.permStorage = stuff;
    726        }
    727
    728        /**
    729         * This should only be used in the case where we want to save the
    730         * data to the database.
    731         *
    732         * @return A Hashtable.
    733         */
    734        public Hashtable getTempStorage()
    735        {
    736            if (this.tempStorage == null)
    737            {
    738                this.tempStorage = new Hashtable();
    739            }
    740            return this.tempStorage;
    741        }
    742
    743        /**
    744         * This should only be used in the case where we want to save the
    745         * data to the database.
    746         *
    747         * @param storage A Hashtable.
    748         */
    749        public void setTempStorage(Hashtable storage)
    750        {
    751            this.tempStorage = storage;
    752        }
    753
    754        /**
    755         * This gets whether or not someone has logged in.  hasLoggedIn()
    756         * returns this value as a boolean.  This is private because you
    757         * should use hasLoggedIn() instead.
    758         *
    759         * @return True if someone has logged in.
    760         */
    761        private Boolean getHasLoggedIn()
    762        {
    763            return (Boolean) getTemp(User.HAS_LOGGED_IN);
    764        }
    765
    766        /**
    767         * This sets whether or not someone has logged in.  hasLoggedIn()
    768         * returns this value.
    769         *
    770         * @param value Whether someone has logged in or not.
    771         */
    772        public void setHasLoggedIn(Boolean value)
    773        {
    774            setTemp(User.HAS_LOGGED_IN, value);
    775        }
    776
    777        /**
    778         * Put an object into temporary storage.
    779         *
    780         * @param name The object's name.
    781         * @param value The object.
    782         */
    783        public void setTemp(String name, Object value)
    784        {
    785            tempStorage.put(name, value);
    786        }
    787
    788        /**
    789         * A User object can have a variable Timeout which is defined in
    790         * minutes.  If the user has been timed out, then the
    791         * hasLoggedIn() value will return false.
    792         *
    793         * @param time The user's timeout.
    794         */
    795        public void setTimeout(int time)
    796        {
    797            this.timeout = time;
    798        }
    799
    800        /**
    801         * Updates the last login date in the database.
    802         *
    803         * @exception Exception a generic exception.
    804         */
    805        public void updateLastLogin() throws Exception
    806        {
    807            setPerm(User.LAST_LOGIN, new java.util.Date());
    808        }
    809
    810        /**
    811         * Implement this method if you wish to be notified when the User
    812         * has been Bound to the session.
    813         *
    814         * @param hsbe The HttpSessionBindingEvent.
    815         */
    816        public void valueBound(HttpSessionBindingEvent hsbe)
    817        {
    818            // Do not currently need this method.
    819        }
    820
    821        /**
    822         * Implement this method if you wish to be notified when the User
    823         * has been Unbound from the session.
    824         *
    825         * @param hsbe The HttpSessionBindingEvent.
    826         */
    827        public void valueUnbound(HttpSessionBindingEvent hsbe)
    828        {
    829            try
    830            {
    831                if (hasLoggedIn())
    832                {
    833                    TurbineSecurity.saveUser(this);
    834                }
    835            }
    836            catch (Exception e)
    837            {
    838                log.error("BaseUser.valueUnbobund(): "
    839                        + e.getMessage());
    840                log.error(e);
    841
    842                // To prevent messages being lost in case the logging system
    843                // goes away before sessions get unbound on servlet container
    844                // shutdown, print the stcktrace to the container's console.
    845                ByteArrayOutputStream ostr = new ByteArrayOutputStream();
    846
    847                e.printStackTrace(new PrintWriter(ostr, true));
    848                String stackTrace = ostr.toString();
    849
    850                System.out.println(stackTrace);
    851            }
    852        }
    853
    854        /**
    855         * Returns the username for this user.  If this is defined, then
    856         * the user is considered logged in.
    857         *
    858         * @return A String with the username.
    859         */
    860        public String getName()
    861        {
    862            String tmp = null;
    863
    864            tmp = (String) getPerm(User.USERNAME);
    865            if (tmp != null && tmp.length() == 0)
    866            {
    867                tmp = null;
    868            }
    869            return tmp;
    870        }
    871
    872        /**
    873         * Set the users name.
    874         * @param name the name of the User.
    875         */
    876        public void setName(String name)
    877        {
    878                    setPerm(User.USERNAME, name);
    879        }
    880
    881        /**
    882         * Not implemented.
    883         * @return 0
    884         */
    885        public int getId()
    886        {
    887            return 0;
    888        }
    889
    890        /**
    891         * Not implemented.
    892         * @return null
    893         */
    894        public Integer getIdAsObj()
    895        {
    896            return new Integer(0);
    897        }
    898
    899        /**
    900         * Not implemented.
    901         *
    902         * @param id The id of the User.
    903         */
    904        public void setId(int id)
    905        {
    906        }
    907
    908        /**
    909         * Saves this object to the data store.
    910         * @throws Exception if it cannot be saved
    911         */
    912        public void save()
    913                throws Exception
    914        {
    915            if (TurbineSecurity.accountExists(this))
    916            {
    917                TurbineSecurity.saveUser(this);
    918            }
    919            else
    920            {
    921                TurbineSecurity.addUser(this, getPassword());
    922            }
    923        }
    924
    925        /**
    926         * not implemented
    927         *
    928         * @param conn the database connection
    929         * @throws Exception if there is an error
    930         */
    931        public void save(Connection conn) throws Exception
    932        {
    933            throw new Exception("not implemented");
    934        }
    935
    936        /**
    937         * not implemented
    938         *
    939         * @param dbname the database name
    940         * @throws Exception if there is an error
    941         */
    942        public void save(String dbname) throws Exception
    943        {
    944            throw new Exception("not implemented");
    945        }
    946
    947    }