001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software GmbH & Co. KG, please see the
018 * company website: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.file;
029
030import org.opencms.db.CmsUserSettings;
031import org.opencms.main.CmsException;
032import org.opencms.main.CmsIllegalArgumentException;
033import org.opencms.main.OpenCms;
034import org.opencms.security.CmsPrincipal;
035import org.opencms.security.CmsSecurityException;
036import org.opencms.security.I_CmsPrincipal;
037import org.opencms.util.CmsMacroResolver;
038import org.opencms.util.CmsStringUtil;
039import org.opencms.util.CmsUUID;
040
041import java.util.Collections;
042import java.util.HashMap;
043import java.util.Locale;
044import java.util.Map;
045import java.util.Objects;
046
047/**
048 * A user principal in the OpenCms permission system.<p>
049 *
050 * A user in OpenCms is uniquely defined by its user named returned by
051 * <code>{@link #getName()}</code>.<p>
052 *
053 * Basic users in OpenCms are users that can access the OpenCms Workplace.
054 * Moreover, the user must be created by another user with the
055 * <code>{@link org.opencms.security.CmsRole#ACCOUNT_MANAGER}</code> role.
056 * These users are "content managers" that actually have write permissions in
057 * at last some parts of the VFS.<p>
058 *
059 * Another possibility is to have users in a 'Guests' group.
060 * These users do not have access to the OpenCms Workplace.
061 * However, an user in a 'Guests' group can be created by every user, for example
062 * the "Guest" user. The main use case is that these users are used for users of
063 * the website that can generate their own accounts, in a "please register your
064 * account..." scenario.
065 * These user accounts can then be used to build personalized web sites.<p>
066 *
067 * @since 6.0.0
068 *
069 * @see CmsGroup
070 */
071public class CmsUser extends CmsPrincipal implements Cloneable {
072
073    /** Flag indicating changed additional infos. */
074    public static final int FLAG_ADDITIONAL_INFOS = 4;
075
076    /** Flag indicating changed core data. */
077    public static final int FLAG_CORE_DATA = 8;
078
079    /** Flag indicating a changed last login date. */
080    public static final int FLAG_LAST_LOGIN = 2;
081
082    /** Storage for additional user information. */
083    private Map<String, Object> m_additionalInfo;
084
085    /** The creation date. */
086    private long m_dateCreated;
087
088    /**  The email of the user. */
089    private String m_email;
090
091    /** The first name of this user. */
092    private String m_firstname;
093
094    /** Boolean flag whether the last-login time stamp of this user was modified. */
095    private boolean m_isTouched;
096
097    /** The last login date of this user. */
098    private long m_lastlogin;
099
100    /** The last name of this user. */
101    private String m_lastname;
102
103    /** The password of this user. */
104    private String m_password;
105
106    /**
107     * Creates a new, empty OpenCms user principal.<p>
108     *
109     * Mostly intended to be used with the <code>org.opencms.workplace.tools.accounts.A_CmsEditUserDialog</code>.<p>
110     */
111    public CmsUser() {
112
113        this(
114            null,
115            "",
116            "",
117            "",
118            "",
119            "",
120            0,
121            I_CmsPrincipal.FLAG_ENABLED,
122            System.currentTimeMillis(),
123            Collections.singletonMap(CmsUserSettings.ADDITIONAL_INFO_DESCRIPTION, (Object)""));
124    }
125
126    /**
127     * Creates a new OpenCms user principal.<p>
128     *
129     * @param id the unique id of the new user
130     * @param name the fully qualified name of the new user
131     * @param password the password of the user
132     * @param firstname the first name
133     * @param lastname the last name
134     * @param email the email address
135     * @param lastlogin time stamp
136     * @param flags flags
137     * @param dateCreated the creation date
138     * @param additionalInfo user related information
139     */
140    public CmsUser(
141        CmsUUID id,
142        String name,
143        String password,
144        String firstname,
145        String lastname,
146        String email,
147        long lastlogin,
148        int flags,
149        long dateCreated,
150        Map<String, Object> additionalInfo) {
151
152        m_id = id;
153        m_name = name;
154        m_password = password;
155        m_firstname = firstname;
156        m_lastname = lastname;
157        m_email = email;
158        m_lastlogin = lastlogin;
159        m_flags = flags;
160        m_dateCreated = dateCreated;
161        if (additionalInfo != null) {
162            m_additionalInfo = new HashMap<String, Object>(additionalInfo);
163        } else {
164            m_additionalInfo = new HashMap<String, Object>();
165        }
166        if (m_additionalInfo.get(CmsUserSettings.ADDITIONAL_INFO_ADDRESS) == null) {
167            m_additionalInfo.put(CmsUserSettings.ADDITIONAL_INFO_ADDRESS, "");
168        }
169        if (m_additionalInfo.get(CmsUserSettings.ADDITIONAL_INFO_DESCRIPTION) == null) {
170            m_additionalInfo.put(CmsUserSettings.ADDITIONAL_INFO_DESCRIPTION, "");
171        }
172    }
173
174    /**
175     * Validates an email address.<p>
176     *
177     * That means, the parameter should only be composed by digits and standard english letters, points, underscores and exact one "At" symbol.<p>
178     *
179     * @param email the email to validate
180     */
181    public static void checkEmail(String email) {
182
183        OpenCms.getValidationHandler().checkEmail(email);
184    }
185
186    /**
187     * Validates a zip code.<p>
188     *
189     * That means, the parameter should only be composed by digits and standard english letters.<p>
190     *
191     * @param zipcode the zip code to validate
192     */
193    public static void checkZipCode(String zipcode) {
194
195        OpenCms.getValidationHandler().checkZipCode(zipcode);
196    }
197
198    /**
199     * Returns the "full" name of the given user in the format <code>"{firstname} {lastname} ({username})"</code>,
200     * or the empty String <code>""</code> if the user is null.<p>
201     *
202     * @param user the user to get the full name from
203     * @return the "full" name the user
204     *
205     * @see #getFullName()
206     */
207    public static String getFullName(CmsUser user) {
208
209        if (user == null) {
210            return "";
211        } else {
212            return user.getFullName();
213        }
214    }
215
216    /**
217     * Checks whether the flag indicates additional info changes.<p>
218     *
219     * @param changes the changes flags
220     *
221     * @return <code>true</code> in case the additional infos changed
222     */
223    public static boolean hasChangedAdditionalInfos(int changes) {
224
225        return (changes & FLAG_ADDITIONAL_INFOS) == FLAG_ADDITIONAL_INFOS;
226    }
227
228    /**
229     * Checks whether the flag indicates core data changes.<p>
230     *
231     * @param changes the changes flags
232     *
233     * @return <code>true</code> in case the core data changed
234     */
235    public static boolean hasChangedCoreData(int changes) {
236
237        return (changes & FLAG_CORE_DATA) == FLAG_CORE_DATA;
238    }
239
240    /**
241     * Checks whether the flag indicates last login date changes.<p>
242     *
243     * @param changes the changes flags
244     *
245     * @return <code>true</code> in case the last login date changed
246     */
247    public static boolean hasChangedLastLogin(int changes) {
248
249        return (changes & FLAG_LAST_LOGIN) == FLAG_LAST_LOGIN;
250    }
251
252    /**
253     * Checks if the given String starts with {@link I_CmsPrincipal#PRINCIPAL_USER} followed by a dot.<p>
254     *
255     * <ul>
256     * <li>Works if the given String is <code>null</code>.
257     * <li>Removes white spaces around the String before the check.
258     * <li>Also works with prefixes not being in upper case.
259     * <li>Does not check if the user after the prefix actually exists.
260     * </ul>
261     *
262     * @param principalName the user name to check
263     *
264     * @return <code>true</code> in case the String starts with {@link I_CmsPrincipal#PRINCIPAL_USER} followed by a dot
265     */
266    public static boolean hasPrefix(String principalName) {
267
268        return CmsStringUtil.isNotEmptyOrWhitespaceOnly(principalName)
269            && (principalName.trim().toUpperCase().startsWith(I_CmsPrincipal.PRINCIPAL_USER + "."));
270    }
271
272    /**
273     * Removes the prefix if the given String starts with {@link I_CmsPrincipal#PRINCIPAL_USER} followed by a dot.<p>
274     *
275     * <ul>
276     * <li>Works if the given String is <code>null</code>.
277     * <li>If the given String does not start with {@link I_CmsPrincipal#PRINCIPAL_USER} followed by a dot it is returned unchanged.
278     * <li>Removes white spaces around the user name.
279     * <li>Also works with prefixes not being in upper case.
280     * <li>Does not check if the user after the prefix actually exists.
281     * </ul>
282     *
283     * @param principalName the user name to remove the prefix from
284     *
285     * @return the given String with the prefix {@link I_CmsPrincipal#PRINCIPAL_USER} and the following dot removed
286     */
287    public static String removePrefix(String principalName) {
288
289        String result = principalName;
290        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(principalName)) {
291            if (hasPrefix(principalName)) {
292                result = principalName.trim().substring(I_CmsPrincipal.PRINCIPAL_USER.length() + 1);
293            }
294        }
295        return result;
296    }
297
298    /**
299     * Checks if the provided user name is a valid user name and can be used as an argument value
300     * for {@link #setName(String)}.<p>
301     *
302     * @param name the user name to check
303     *
304     * @throws CmsIllegalArgumentException if the check fails
305     */
306    public void checkName(String name) throws CmsIllegalArgumentException {
307
308        OpenCms.getValidationHandler().checkUserName(name);
309    }
310
311    /**
312     * @see java.lang.Object#clone()
313     */
314    @Override
315    public CmsUser clone() {
316
317        return new CmsUser(
318            m_id,
319            m_name,
320            m_password,
321            m_firstname,
322            m_lastname,
323            m_email,
324            m_lastlogin,
325            m_flags,
326            m_dateCreated,
327            new HashMap<String, Object>(m_additionalInfo));
328    }
329
330    /**
331     * Deletes a value from this users "additional information" storage map.<p>
332     *
333     * @param key the additional user information to delete
334     *
335     * @see #getAdditionalInfo()
336     */
337    public void deleteAdditionalInfo(String key) {
338
339        m_additionalInfo.remove(key);
340    }
341
342    /**
343     * Returns this users complete "additional information" storage map.<p>
344     *
345     * The "additional information" storage map is a simple {@link java.util.Map}
346     * that can be used to store any key / value pairs for the user.
347     * Some information parts of the users address are stored in this map
348     * by default.<p>
349     *
350     * @return this users complete "additional information" storage map
351     */
352    public Map<String, Object> getAdditionalInfo() {
353
354        return m_additionalInfo;
355    }
356
357    /**
358     * Returns a value from this users "additional information" storage map,
359     * or <code>null</code> if no value for the given key is available.<p>
360     *
361     * @param key selects the value to return from the "additional information" storage map
362     *
363     * @return the selected value from this users "additional information" storage map
364     *
365     * @see #getAdditionalInfo()
366     */
367    public Object getAdditionalInfo(String key) {
368
369        return m_additionalInfo.get(key);
370    }
371
372    /**
373     * Returns the address line of this user.<p>
374     *
375     * @return the address line of this user
376     */
377    public String getAddress() {
378
379        return (String)getAdditionalInfo(CmsUserSettings.ADDITIONAL_INFO_ADDRESS);
380    }
381
382    /**
383     * Returns the changes of this user compared to the previous user data.<p>
384     *
385     * @param oldUser the old user
386     *
387     * @return the changes flags
388     */
389    public int getChanges(CmsUser oldUser) {
390
391        int result = 0;
392        if (oldUser.m_lastlogin != m_lastlogin) {
393            result = result | FLAG_LAST_LOGIN;
394        }
395        if (!oldUser.m_additionalInfo.equals(m_additionalInfo)) {
396            result = result | FLAG_ADDITIONAL_INFOS;
397        }
398        if (!Objects.equals(oldUser.m_email, m_email)
399            || !Objects.equals(oldUser.m_description, m_description)
400            || !Objects.equals(oldUser.m_firstname, m_firstname)
401            || !Objects.equals(oldUser.m_lastname, m_lastname)
402            || !Objects.equals(oldUser.m_password, m_password)
403            || (oldUser.m_flags != m_flags)) {
404            result = result | FLAG_CORE_DATA;
405        }
406        return result;
407    }
408
409    /**
410     * Returns the city information of this user.<p>
411     *
412     * This information is stored in the "additional information" storage map
413     * using the key <code>{@link CmsUserSettings#ADDITIONAL_INFO_CITY}</code>.<p>
414     *
415     * @return the city information of this user
416     */
417    public String getCity() {
418
419        return (String)getAdditionalInfo(CmsUserSettings.ADDITIONAL_INFO_CITY);
420    }
421
422    /**
423     * Returns the country information of this user.<p>
424     *
425     * This information is stored in the "additional information" storage map
426     * using the key <code>{@link CmsUserSettings#ADDITIONAL_INFO_COUNTRY}</code>.<p>
427     *
428     * @return the country information of this user
429     */
430    public String getCountry() {
431
432        return (String)getAdditionalInfo(CmsUserSettings.ADDITIONAL_INFO_COUNTRY);
433    }
434
435    /**
436     * Returns the creation date.<p>
437     *
438     * @return the creation date
439     */
440    public long getDateCreated() {
441
442        return m_dateCreated;
443    }
444
445    /**
446     * @see org.opencms.security.CmsPrincipal#getDescription()
447     */
448    @Override
449    public String getDescription() {
450
451        return (String)getAdditionalInfo(CmsUserSettings.ADDITIONAL_INFO_DESCRIPTION);
452    }
453
454    /**
455     * Returns the description of this organizational unit.<p>
456     *
457     * @param locale the locale
458     *
459     * @return the description of this organizational unit
460     */
461    public String getDescription(Locale locale) {
462
463        CmsMacroResolver macroResolver = new CmsMacroResolver();
464        macroResolver.setMessages(org.opencms.db.generic.Messages.get().getBundle(locale));
465        return macroResolver.resolveMacros((String)getAdditionalInfo(CmsUserSettings.ADDITIONAL_INFO_DESCRIPTION));
466    }
467
468    /**
469     * @see org.opencms.security.CmsPrincipal#getDisplayName(org.opencms.file.CmsObject, java.util.Locale)
470     */
471    @Override
472    public String getDisplayName(CmsObject cms, Locale locale) throws CmsException {
473
474        if (OpenCms.getOrgUnitManager().getOrganizationalUnits(cms, "", true).size() > 0) {
475            return org.opencms.security.Messages.get().getBundle(locale).key(
476                org.opencms.security.Messages.GUI_PRINCIPAL_DISPLAY_NAME_2,
477                getFullName(),
478                OpenCms.getOrgUnitManager().readOrganizationalUnit(cms, getOuFqn()).getDisplayName(locale));
479        } else {
480            return getFullName();
481        }
482    }
483
484    /**
485     * Returns the email address of this user.<p>
486     *
487     * @return the email address of this user
488     */
489    public String getEmail() {
490
491        return m_email;
492    }
493
494    /**
495     * Returns the first name of this user.<p>
496     *
497     * @return the first name of this user
498     */
499    public String getFirstname() {
500
501        return m_firstname;
502    }
503
504    /**
505     * Returns the "full" name of the this user in the format <code>"{firstname} {lastname} ({username})"</code>.<p>
506     *
507     * @return the "full" name this user
508     */
509    public String getFullName() {
510
511        StringBuffer buf = new StringBuffer();
512        String first = getFirstname();
513        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(first)) {
514            buf.append(first);
515            buf.append(" ");
516        }
517        String last = getLastname();
518        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(last)) {
519            buf.append(last);
520            buf.append(" ");
521        }
522        buf.append("(");
523        buf.append(getSimpleName());
524        buf.append(")");
525        return buf.toString();
526    }
527
528    /**
529     * Returns the institution information of this user.<p>
530     *
531     * This information is stored in the "additional information" storage map
532     * using the key <code>{@link CmsUserSettings#ADDITIONAL_INFO_INSTITUTION}</code>.<p>
533     *
534     * @return the institution information of this user
535     */
536    public String getInstitution() {
537
538        return (String)getAdditionalInfo(CmsUserSettings.ADDITIONAL_INFO_INSTITUTION);
539    }
540
541    /**
542     * Returns the time of the last login of this user.<p>
543     *
544     * @return the time of the last login of this user
545     */
546    public long getLastlogin() {
547
548        return m_lastlogin;
549    }
550
551    /**
552     * Returns the last name of this user.<p>
553     *
554     * @return the last name of this user
555     */
556    public String getLastname() {
557
558        return m_lastname;
559    }
560
561    /**
562     * Returns the encrypted user password.<p>
563     *
564     * @return the encrypted user password
565     */
566    public String getPassword() {
567
568        return m_password;
569    }
570
571    /**
572     * Returns the zip code information of this user.<p>
573     *
574     * This information is stored in the "additional information" storage map
575     * using the key <code>{@link CmsUserSettings#ADDITIONAL_INFO_ZIPCODE}</code>.<p>
576     *
577     * @return the zip code information of this user
578     */
579    public String getZipcode() {
580
581        return (String)getAdditionalInfo(CmsUserSettings.ADDITIONAL_INFO_ZIPCODE);
582    }
583
584    /**
585     * @see org.opencms.security.I_CmsPrincipal#isGroup()
586     */
587    @Override
588    public boolean isGroup() {
589
590        return false;
591    }
592
593    /**
594     * Checks if this user is the default guest user.<p>
595     *
596     * @return <code>true</code> if this user is the default guest user
597     */
598    public boolean isGuestUser() {
599
600        return OpenCms.getDefaultUsers().isUserGuest(getName());
601    }
602
603    /**
604     * Returns <code>true</code> if this user is not able to manage itself.<p>
605     *
606     * @return <code>true</code> if this user is not able to manage itself
607     */
608    public boolean isManaged() {
609
610        return (getFlags() & I_CmsPrincipal.FLAG_USER_MANAGED) == I_CmsPrincipal.FLAG_USER_MANAGED;
611    }
612
613    /**
614     * Returns <code>true</code> if this user was touched.<p>
615     *
616     * @return boolean true if this user was touched
617     */
618    public boolean isTouched() {
619
620        return m_isTouched;
621    }
622
623    /**
624     * @see org.opencms.security.I_CmsPrincipal#isUser()
625     */
626    @Override
627    public boolean isUser() {
628
629        return true;
630    }
631
632    /**
633     * Checks if the user is marked as webuser.<p>
634     *
635     * @return <code>true</code> if the user is marked as webuser
636     */
637    public boolean isWebuser() {
638
639        return (getFlags() & FLAG_USER_WEBUSER) == FLAG_USER_WEBUSER;
640    }
641
642    /**
643     * Sets this users complete "additional information" storage map to the given value.<p>
644     *
645     * @param additionalInfo the complete "additional information" map to set
646     *
647     * @see #getAdditionalInfo()
648     */
649    public void setAdditionalInfo(Map<String, Object> additionalInfo) {
650
651        m_additionalInfo = additionalInfo;
652    }
653
654    /**
655     * Stores a value in this users "additional information" storage map with the given access key.<p>
656     *
657     * @param key the key to store the value under
658     * @param value the value to store in the users "additional information" storage map
659     *
660     * @see #getAdditionalInfo()
661     */
662    public void setAdditionalInfo(String key, Object value) {
663
664        if (key == null) {
665            throw new CmsIllegalArgumentException(
666                Messages.get().container(Messages.ERR_USER_ADDINFO_KEY_NULL_1, getFullName()));
667        }
668        m_additionalInfo.put(key, value);
669    }
670
671    /**
672     * Sets the address line of this user.<p>
673     *
674     * @param address the address line to set
675     */
676    public void setAddress(String address) {
677
678        setAdditionalInfo(CmsUserSettings.ADDITIONAL_INFO_ADDRESS, address);
679    }
680
681    /**
682     * Sets the city information of this user.<p>
683     *
684     * @param city the city information to set
685     */
686    public void setCity(String city) {
687
688        setAdditionalInfo(CmsUserSettings.ADDITIONAL_INFO_CITY, city);
689    }
690
691    /**
692     * Sets the country information of this user.<p>
693     *
694     * @param country the city information to set
695     */
696    public void setCountry(String country) {
697
698        setAdditionalInfo(CmsUserSettings.ADDITIONAL_INFO_COUNTRY, country);
699    }
700
701    /**
702     * @see org.opencms.security.CmsPrincipal#setDescription(java.lang.String)
703     */
704    @Override
705    public void setDescription(String description) {
706
707        setAdditionalInfo(CmsUserSettings.ADDITIONAL_INFO_DESCRIPTION, description);
708    }
709
710    /**
711     * Sets the email address of this user.<p>
712     *
713     * @param email the email address to set
714     */
715    public void setEmail(String email) {
716
717        checkEmail(email);
718        if (email != null) {
719            email = email.trim();
720        }
721        m_email = email;
722    }
723
724    /**
725     * Sets the first name of this user.<p>
726     *
727     * @param firstname the name to set
728     */
729    public void setFirstname(String firstname) {
730
731        OpenCms.getValidationHandler().checkFirstname(firstname);
732        if (firstname != null) {
733            firstname = firstname.trim();
734        }
735        m_firstname = firstname;
736    }
737
738    /**
739     * Sets the institution information of this user.<p>
740     *
741     * @param institution the institution information to set
742     */
743    public void setInstitution(String institution) {
744
745        setAdditionalInfo(CmsUserSettings.ADDITIONAL_INFO_INSTITUTION, institution);
746    }
747
748    /**
749     * Sets the last login time stamp of this user.<p>
750     *
751     * @param value the last login time stamp to set
752     */
753    public void setLastlogin(long value) {
754
755        m_isTouched = true;
756        m_lastlogin = value;
757    }
758
759    /**
760     * Sets the last name of this user.<p>
761     *
762     * @param lastname the name to set
763     */
764    public void setLastname(String lastname) {
765
766        OpenCms.getValidationHandler().checkLastname(lastname);
767        if (lastname != null) {
768            lastname = lastname.trim();
769        }
770        m_lastname = lastname;
771    }
772
773    /**
774     * Sets the managed flag for this user to the given value.<p>
775     *
776     * @param value the value to set
777     */
778    public void setManaged(boolean value) {
779
780        if (isManaged() != value) {
781            setFlags(getFlags() ^ I_CmsPrincipal.FLAG_USER_MANAGED);
782        }
783    }
784
785    /**
786     * Sets the password of this user.<p>
787     *
788     * @param value the password to set
789     */
790    public void setPassword(String value) {
791
792        try {
793            OpenCms.getPasswordHandler().validatePassword(value);
794        } catch (CmsSecurityException e) {
795            throw new CmsIllegalArgumentException(e.getMessageContainer());
796        }
797        m_password = value;
798    }
799
800    /**
801     * Sets the zip code information of this user.<p>
802     *
803     * @param zipcode the zip code information to set
804     */
805    public void setZipcode(String zipcode) {
806
807        checkZipCode(zipcode);
808        if (zipcode != null) {
809            zipcode = zipcode.toUpperCase();
810        }
811        setAdditionalInfo(CmsUserSettings.ADDITIONAL_INFO_ZIPCODE, zipcode);
812    }
813
814    /**
815     * @see java.lang.Object#toString()
816     */
817    @Override
818    public String toString() {
819
820        StringBuffer result = new StringBuffer();
821        result.append("[User]");
822        result.append(" name:");
823        result.append(getName());
824        result.append(" id:");
825        result.append(m_id);
826        result.append(" flags:");
827        result.append(getFlags());
828        result.append(" description:");
829        result.append(getDescription());
830        return result.toString();
831    }
832
833    /**
834     * Sets the "touched" status of this user to <code>true</code>.<p>
835     */
836    public void touch() {
837
838        m_isTouched = true;
839    }
840}