All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.efaps.admin.user.Person Maven / Gradle / Ivy

Go to download

eFaps is a framework used to map objects with or without attached files to a relational database and optional file systems (only for attaches files). Configurable access control can be provided down to object and attribute level depending on implementation and use case. Depending on requirements, events (like triggers) allow to implement business logic and to separate business logic from user interface. The framework includes integrations (e.g. webdav, full text search) and a web application as 'simple' configurable user interface. Some best practises, example web application modules (e.g. team work module) support administrators and implementers using this framework.

There is a newer version: 3.2.0
Show newest version
/*
 * Copyright 2003 - 2010 The eFaps Team
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Revision:        $Rev: 6622 $
 * Last Changed:    $Date: 2011-06-09 21:32:14 -0500 (Thu, 09 Jun 2011) $
 * Last Changed By: $Author: [email protected] $
 */

package org.efaps.admin.user;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID;

import org.apache.commons.lang.builder.ToStringBuilder;
import org.efaps.admin.EFapsSystemConfiguration;
import org.efaps.admin.common.SystemConfiguration;
import org.efaps.admin.datamodel.Type;
import org.efaps.admin.datamodel.attributevalue.PasswordStore;
import org.efaps.ci.CIAdminUser;
import org.efaps.db.Context;
import org.efaps.db.PrintQuery;
import org.efaps.db.Update;
import org.efaps.db.Update.Status;
import org.efaps.db.transaction.ConnectionResource;
import org.efaps.jaas.AppAccessHandler;
import org.efaps.util.ChronologyType;
import org.efaps.util.DateTimeUtil;
import org.efaps.util.EFapsException;
import org.efaps.util.cache.AbstractCache;
import org.efaps.util.cache.CacheReloadException;
import org.joda.time.Chronology;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Class represents the instance of a person/user in eFaps.
 *
 * @author The eFasp Team
 * @version $Id: Person.java 6622 2011-06-10 02:32:14Z [email protected] $
 */
public final class Person
    extends AbstractUserObject
{

    /**
     * Enum for all known and updated attributes from a person. Only this could
     * be defined which are in the SQL table T_USERPERSON.
     */
    public enum AttrName {
        /** Attribute Name for the First Name of the person. */
        FIRSTNAME("FIRSTNAME"),
        /** Attribute Name for the Last Name of the person. */
        LASTNAME("LASTNAME"),
        /** Attribute Name for the Chronology of the person. */
        CHRONOLOGY("CHRONOLOGY"),
        /** Attribute Name for the Timezone of the person. */
        TIMZONE("TIMZONE"),
        /** Attribute Name for the Locale of the person. */
        LOCALE("LOCALE"),
         /** Attribute Name for the language of the person. */
         LANGUAGE("LANG", true);

        /**
         * The name of the depending SQL column for an attribute in the table.
         */
        private final String sqlColumn;

        /**
         * The name of the depending SQL column for an attribute in the table.
         */
        private final boolean integer;

        /**
         * Constructor setting the instance variables.
         *
         * @param _sqlColumn name of the column in the table
         */
        private AttrName(final String _sqlColumn)
        {
            this(_sqlColumn, false);
        }

        /**
         * Constructor setting the instance variables.
         *
         * @param _sqlColumn name of the column in the table
         * @param _integer is the column a integer column
         */
        private AttrName(final String _sqlColumn,
                         final boolean _integer)
        {
            this.sqlColumn = _sqlColumn;
            this.integer = _integer;
        }
    }

    /**
     * Logging instance used to give logging information of this class.
     */
    private static final Logger LOG = LoggerFactory.getLogger(Person.class);

    /**
     * Stores all instances of class {@link Person}.
     *
     * @see #getCache
     */
    private static final AbstractCache CACHE = new PersonCache();

    /**
     * Key to access the cache4Name in the Context.
     */
    private static final String CACHE4NAMEKEY = "org.efaps.admin.user.Person.eFapsPersonCache4Name";

    /**
     * Key to access the cache4ID in the Context.
     */
    private static final String CACHE4IDKEY = "org.efaps.admin.user.Person.eFapsPersonCache4Id";

    /**
     * HashSet instance variable to hold all id of roles for this person.
     *
     * @see #getRoles
     * @see #add(Role)
     */
    private final Set roles = new HashSet();

    /**
     * HashSet instance variable to hold all id of groups for this person.
     *
     * @see #getGroups
     * @see #add(Group)
     */
    private final Set groups = new HashSet();

    /**
     * HashSet instance variable to hold all id of groups for this person.
     *
     * @see #getCompanies
     * @see #add(Company)
     */
    private final Set companies = new HashSet();

    /**
     * The map is used to store all attribute values depending on attribute
     * names defined in {@link #AttrName}.
     *
     * @see #setAttrValue
     * @see #updateAttrValue
     * @see #AttrName
     */
    private final Map attrValues = new HashMap();

    /**
     * The map is used to store information about updates on attribute values.
     * This information is needed if the database must be updated.
     *
     * @see #updateAttrValue
     * @see #commitAttrValuesInDB
     * @see #AttrName
     */
    private final Map attrUpdated = new HashMap();

    /**
     * The constructor creates a new instance of class {@link Person} and sets
     * the {@link #key} and {@link #id}.
     *
     * @param _id id of the person to set
     * @param _name name of the person to set
     * @param _status status of the person to set
     */
    private Person(final long _id,
                   final String _name,
                   final boolean _status)
    {
        super(_id, null, _name, _status);
    }

    /**
     * Checks, if the given person is assigned to this user object. Here it is
     * only tested if the person is the same as the user of the parameter.
     *
     * @param _person person to test
     * @return true if the person is the same person as this person,
     *         otherwise false
     */
    @Override
    public boolean hasChildPerson(final Person _person)
    {
        return _person.getId() == getId();
    }

    /**
     * Add a role to this person.
     *
     * @param _role role to add to this person
     * @see #roles
     */
    private void add(final Role _role)
    {
        this.roles.add(_role.getId());
    }

    /**
     * Tests, if the given role is assigned to this person.
     *
     * @param _role role to test
     * @return true if role is assigned to this person, otherwise
     *         false
     */
    public boolean isAssigned(final Role _role)
    {
        return this.roles.contains(_role.getId());
    }

    /**
     * Add a role to this person.
     *
     * @param _group group to add to this person
     * @see #groups
     */
    private void add(final Group _group)
    {
        this.groups.add(_group.getId());
    }

    /**
     * Tests, if the given group is assigned to this person.
     *
     * @param _group group to test
     * @return true if group is assigned to this person, otherwise
     *         false
     */
    public boolean isAssigned(final Group _group)
    {
        return this.groups.contains(_group.getId());
    }

    /**
     * Add a role to this person.
     *
     * @param _group group to add to this person
     * @see #groups
     */
    private void add(final Company _group)
    {
        this.companies.add(_group.getId());
    }

    /**
     * Tests, if the given group is assigned to this person.
     *
     * @param _company Company to test
     * @return true if group is assigned to this person, otherwise
     *         false
     */
    public boolean isAssigned(final Company _company)
    {
        return this.companies.contains(_company.getId());
    }

    /**
     * All assigned roles in {@link #roles} and groups in {@link #groups} are
     * removed in the cache from this person instance. This is needed if the
     * person assignments are rebuild (e.g. from a login servlet).
     */
    public void cleanUp()
    {
        this.roles.clear();
        this.groups.clear();
        this.companies.clear();
    }

    /**
     * The method sets the attribute values in the cache for given attribute
     * name to given new attribute value.
     *
     * @param _attrName name of attribute to set
     * @param _value new value to set
     * @see #attrValues
     */
    private void setAttrValue(final AttrName _attrName,
                              final String _value)
    {
        synchronized (this.attrValues) {
            this.attrValues.put(_attrName, _value);
        }
    }

    /**
     * Returns for given attribute name the value in the cache.
     *
     * @param _attrName name of attribute for which the value must returned
     * @return attribute value of given attribute name
     */
    public String getAttrValue(final AttrName _attrName)
    {
        return this.attrValues.get(_attrName);
    }

    /**
     * @return attribute value of first name
     */
    public String getFirstName()
    {
        return this.attrValues.get(Person.AttrName.FIRSTNAME);
    }

    /**
     * @return attribute value of last name
     */
    public String getLastName()
    {
        return this.attrValues.get(Person.AttrName.LASTNAME);
    }

    /**
     * Method to get the Locale of this Person. Default is the "English" Locale.
     *
     * @return Locale of this Person
     */
    public Locale getLocale()
    {
        final Locale ret;
        if (this.attrValues.get(Person.AttrName.LOCALE) != null) {
            final String localeStr = this.attrValues.get(Person.AttrName.LOCALE);
            final String[] countries = localeStr.split("_");
            if (countries.length == 2) {
                ret = new Locale(countries[0], countries[1]);
            } else if (countries.length == 3) {
                ret = new Locale(countries[0], countries[1], countries[2]);
            } else {
                ret = new Locale(localeStr);
            }
        } else {
            ret = Locale.ENGLISH;
        }
        return ret;
    }

    /**
     * Method to get the Language of the UserInterface for this Person. Default
     * is english.
     *
     * @return iso code of a language
     */
    public String getLanguage()
    {
        return this.attrValues.get(Person.AttrName.LANGUAGE) != null
                        ? this.attrValues.get(Person.AttrName.LANGUAGE)
                        : Locale.ENGLISH.getISO3Language();
    }

    /**
     * Method to get the Timezone of this Person. Default is the "UTC" Timezone.
     *
     * @return Timezone of this Person
     */
    public DateTimeZone getTimeZone()
    {
        return this.attrValues.get(Person.AttrName.TIMZONE) != null
                        ? DateTimeZone.forID(this.attrValues.get(Person.AttrName.TIMZONE))
                        : DateTimeZone.UTC;
    }

    /**
     * Method to get the Chronology of this Person. Default is the "ISO8601"
     * Chronology.
     *
     * @return Chronology of this Person
     */
    public Chronology getChronology()
    {
        return getChronologyType().getInstance(getTimeZone());
    }

    /**
     * Method to get the ChronologyType of this Person. Default is the "ISO8601"
     * ChronologyType.
     *
     * @return ChronologyType of this Person
     */
    public ChronologyType getChronologyType()
    {
        final String chronoKey = this.attrValues.get(Person.AttrName.CHRONOLOGY);
        final ChronologyType chronoType;
        if (chronoKey != null) {
            chronoType = ChronologyType.getByKey(chronoKey);
        } else {
            chronoType = ChronologyType.ISO8601;
        }
        return chronoType;
    }

    /**
     * Updates a value for an attribute in the cache and marks then as modified.
     * Only after calling method {@link #commitAttrValuesInDB} the updated
     * attribute value is stored in the database!
     *
     * @param _attrName name of attribute to update
     * @param _value new value to set directly
     */
    public void updateAttrValue(final AttrName _attrName,
                                final String _value)
    {
        this.updateAttrValue(_attrName, _value, _value);
    }

    /**
     * Updates a value for an attribute in the cache and marks then as modified.
     * Only after calling method {@link #commitAttrValuesInDB} the updated
     * attribute value is stored in the database!
     *
     * @param _attrName name of attribute to update
     * @param _value new value to set directly
     * @param _updateValue new value to be set in the database
     * @see #attrUpdated
     * @see #attrValues
     */
    public void updateAttrValue(final AttrName _attrName,
                                final String _value,
                                final String _updateValue)
    {
        synchronized (this.attrUpdated) {
            synchronized (this.attrValues) {
                this.attrValues.put(_attrName, _value);
            }
            this.attrUpdated.put(_attrName, _updateValue);
        }
    }

    /**
     * Commits update attribute defined in {@link #attrUpdated} with method
     * {@link #updateAttrValue} to the database. After database update,
     * {@link #attrUpdated} is cleared.
     *
     * @throws EFapsException on error
     * @see #attrUpdated
     * @see #attrValues
     * @see #updateAttrValue
     *
     */
    public void commitAttrValuesInDB()
        throws EFapsException
    {
        synchronized (this.attrUpdated) {
            if (this.attrUpdated.size() > 0) {
                ConnectionResource rsrc = null;
                try {
                    final Context context = Context.getThreadContext();
                    rsrc = context.getConnectionResource();

                    final StringBuilder cmd = new StringBuilder();
                    PreparedStatement stmt = null;
                    try {
                        cmd.append("update T_USERPERSON set ");
                        boolean first = true;
                        for (final AttrName attrName : this.attrUpdated.keySet()) {
                            if (first) {
                                first = false;
                            } else {
                                cmd.append(",");
                            }
                            cmd.append(attrName.sqlColumn).append("=?");
                        }
                        cmd.append(" where ID=").append(getId());
                        stmt = rsrc.getConnection().prepareStatement(cmd.toString());

                        int col = 1;
                        for (final AttrName attrName : this.attrUpdated.keySet()) {
                            final String tmp = this.attrUpdated.get(attrName);
                            if (attrName.integer) {
                                stmt.setInt(col, tmp == null ? 0 : Integer.parseInt(tmp.trim()));
                            } else {
                                stmt.setString(col, tmp == null ? null : tmp.trim());
                            }
                            col++;
                        }

                        final int rows = stmt.executeUpdate();
                        if (rows == 0) {
                            Person.LOG.error("could not update '" + cmd.toString() + "' person with user name '"
                                            + getName() + "' (id = " + getId() + ")");
                            throw new EFapsException(Person.class, "commitAttrValuesInDB.NotUpdated", cmd.toString(),
                                            getName(), getId());
                        }
                        // TODO: update modified date
                    } catch (final SQLException e) {
                        Person.LOG.error("could not update '" + cmd.toString() + "' person with user name '" + getName()
                                        + "' (id = " + getId() + ")", e);
                        throw new EFapsException(Person.class, "commitAttrValuesInDB.SQLException", e, cmd.toString(),
                                        getName(), getId());
                    } finally {
                        try {
                            if (stmt != null) {
                                stmt.close();
                            }
                        } catch (final SQLException e) {
                            throw new EFapsException(Person.class, "commitAttrValuesInDB.SQLException", e, cmd
                                            .toString(), getName(), getId());
                        }
                    }
                    rsrc.commit();
                } finally {
                    if ((rsrc != null) && rsrc.isOpened()) {
                        rsrc.abort();
                    }
                }
                this.attrUpdated.clear();
            }
        }
    }

    /**
     * The instance method checks if the given password is the same password as
     * the password in the database.
     *
     * @param _passwd password to check for this person
     * @return true if password is correct, otherwise false
     * @throws EFapsException if query for the password check failed
     */
    public boolean checkPassword(final String _passwd)
        throws EFapsException
    {
        boolean ret = false;
        final PrintQuery query = new PrintQuery(CIAdminUser.Person.getType(), getId());
        query.addAttribute(CIAdminUser.Person.Password,
                           CIAdminUser.Person.LastLogin,
                           CIAdminUser.Person.LoginTry,
                           CIAdminUser.Person.LoginTriesCounter,
                           CIAdminUser.Person.Status);
        if (query.execute()) {
            final PasswordStore pwd = query.getAttribute(CIAdminUser.Person.Password);
            if (pwd.checkCurrent(_passwd)) {
                ret = query.getAttribute(CIAdminUser.Person.Status);
            } else {
                setFalseLogin(query.getAttribute(CIAdminUser.Person.LoginTry),
                                query.getAttribute(CIAdminUser.Person.LoginTriesCounter));
            }
        }
        return ret;
    }

    /**
     * Method that sets the time and the number of failed logins.
     *
     * @param _logintry time of the false Login
     * @param _count number of tries
     * @throws EFapsException on error
     */
    private void setFalseLogin(final DateTime _logintry,
                               final int _count)
        throws EFapsException
    {
        if (_count > 0) {
            final Timestamp now = DateTimeUtil.getCurrentTimeFromDB();
            final SystemConfiguration kernelConfig = EFapsSystemConfiguration.KERNEL.get();

            // Admin_User_LoginTimeBeforeRetry
            final int dif = kernelConfig.getAttributeValueAsInteger("LoginTimeBeforeRetry");

            // Admin_User_LoginTries
            final int maxtries = kernelConfig.getAttributeValueAsInteger("LoginTries");

            final int count = _count + 1;
            if (dif > 0 && (now.getTime() - _logintry.getMillis()) > dif * 60 * 1000) {
                updateFalseLoginDB(1);
            } else {
                updateFalseLoginDB(count);
            }
            if (maxtries > 0 && count > maxtries && getStatus()) {
                setStatusInDB(false);
            }
        } else {
            updateFalseLoginDB(1);
        }
    }

    /**
     * Method to set the number of false Login tries in the eFaps-DataBase.
     *
     * @param _tries number or tries
     * @throws EFapsException on error
     */
    private void updateFalseLoginDB(final int _tries)
        throws EFapsException
    {
        ConnectionResource rsrc = null;
        try {
            final Context context = Context.getThreadContext();
            rsrc = context.getConnectionResource();

            Statement stmt = null;
            final StringBuilder cmd = new StringBuilder();
            try {

                cmd.append("update T_USERPERSON ").append("set LOGINTRY=").append(
                                Context.getDbType().getCurrentTimeStamp()).append(", LOGINTRIES=").append(_tries)
                                .append(" where ID=").append(getId());
                stmt = rsrc.getConnection().createStatement();
                final int rows = stmt.executeUpdate(cmd.toString());
                if (rows == 0) {
                    Person.LOG.error("could not execute '" + cmd.toString()
                                    + "' to update last login information for person '" + toString() + "'");
                    throw new EFapsException(getClass(), "updateLastLogin.NotUpdated", cmd.toString(), getName());
                }
            } catch (final SQLException e) {
                Person.LOG.error("could not execute '" + cmd.toString()
                                + "' to update last login information for person '" + toString() + "'", e);
                throw new EFapsException(getClass(), "updateLastLogin.SQLException", e, cmd.toString(), getName());
            } finally {
                try {
                    if (stmt != null) {
                        stmt.close();
                    }
                } catch (final SQLException e) {
                    throw new EFapsException(getClass(), "updateLastLogin.SQLException", e, cmd.toString(), getName());
                }
            }
            rsrc.commit();
        } finally {
            if ((rsrc != null) && rsrc.isOpened()) {
                rsrc.abort();
            }
        }
    }

    /**
     * The instance method sets the new password for the current context user.
     * Before the new password is set, some checks are made.
     *
     * @param _newPasswd new Password to set
     * @throws EFapsException on error
     * @return true if password set, else false
     */
    public Status setPassword(final String _newPasswd)
        throws EFapsException
    {
        final Type type = CIAdminUser.Person.getType();
        if (_newPasswd.length() == 0) {
            throw new EFapsException(getClass(), "PassWordLength", 1, _newPasswd.length());
        }
        final Update update = new Update(type, "" + getId());
        final Status status = update.add(CIAdminUser.Person.Password, _newPasswd);
        if (status.isOk()) {
            update.execute();
            update.close();
        } else {
            Person.LOG.error("Password could not be set by the Update, due to restrictions " + "e.g. length???");
            throw new EFapsException(getClass(), "TODO");
        }
        return status;
    }

    /**
     * The instance method reads all information from the database.
     *
     * @throws EFapsException on error
     * @see #readFromDBAttributes()
     */
    protected void readFromDB()
        throws EFapsException
    {
        readFromDBAttributes();
        this.roles.clear();
        for (final Role role : getRolesFromDB()) {
            add(role);
        }
        this.groups.clear();
        for (final Group group : getGroupsFromDB(null)) {
            add(group);
        }
        this.companies.clear();
        for (final Company company : getCompaniesFromDB(null)) {
            add(company);
        }
    }

    /**
     * All attributes from this person are read from the database.
     *
     * @throws EFapsException if the attributes for this person could not be
     *             read
     */
    private void readFromDBAttributes()
        throws EFapsException
    {
        ConnectionResource rsrc = null;
        try {
            rsrc = Context.getThreadContext().getConnectionResource();
            Statement stmt = null;
            try {
                stmt = rsrc.getConnection().createStatement();

                final StringBuilder cmd = new StringBuilder("select ");
                for (final AttrName attrName : Person.AttrName.values()) {
                    cmd.append(attrName.sqlColumn).append(",");
                }
                cmd.append("0 as DUMMY ").append("from V_USERPERSON ").append("where V_USERPERSON.ID=").append(getId());

                final ResultSet resultset = stmt.executeQuery(cmd.toString());
                if (resultset.next()) {
                    for (final AttrName attrName : Person.AttrName.values()) {
                        final String tmp = resultset.getString(attrName.sqlColumn);
                        setAttrValue(attrName, tmp == null ? null : tmp.trim());
                    }
                }
                resultset.close();
            } catch (final SQLException e) {
                Person.LOG.error("read attributes for person with SQL statement is not " + "possible", e);
                throw new EFapsException(Person.class, "readFromDBAttributes.SQLException", e, getName(), getId());
            } finally {
                try {
                    if (stmt != null) {
                        stmt.close();
                    }
                } catch (final SQLException e) {
                    Person.LOG.error("close of SQL statement is not possible", e);
                }
            }
            rsrc.commit();
        } finally {
            if ((rsrc != null) && rsrc.isOpened()) {
                rsrc.abort();
            }
        }
    }

    /**
     * The method reads directly from the database all stored companies for this
     * person. The found roles are returned as instance of {@link Set}.
     *
     * @param _jaasSystem JAAS system for which the roles must get from eFaps
     *            (if value is null, all companies independent from
     *            the related JAAS system are returned)
     * @return set of all found companies for given JAAS system
     * @throws EFapsException on error
     */
    public Set getCompaniesFromDB(final JAASSystem _jaasSystem)
        throws EFapsException
    {
        final Set ret = new HashSet();
        ConnectionResource rsrc = null;
        try {
            rsrc = Context.getThreadContext().getConnectionResource();

            Statement stmt = null;

            try {
                final StringBuilder cmd = new StringBuilder();
                cmd.append("select ").append("USERABSTRACTTO ").append("from V_USERPERSON2COMPANY ")
                                .append("where USERABSTRACTFROM=").append(getId());

                if (_jaasSystem != null) {
                    cmd.append(" and JAASSYSID=").append(_jaasSystem.getId());
                }

                stmt = rsrc.getConnection().createStatement();
                final ResultSet resultset = stmt.executeQuery(cmd.toString());
                while (resultset.next()) {
                    ret.add(Company.get(resultset.getLong(1)));
                }
                resultset.close();

            } catch (final SQLException e) {
                throw new EFapsException(getClass(), "getCompaniesFromDB.SQLException", e, getName());
            } finally {
                try {
                    if (stmt != null) {
                        stmt.close();
                    }
                } catch (final SQLException e) {
                    throw new EFapsException(getClass(), "getCompaniesFromDB.SQLException", e, getName());
                }
            }
            rsrc.commit();
        } finally {
            if ((rsrc != null) && rsrc.isOpened()) {
                rsrc.abort();
            }
        }
        return ret;
    }

    /**
     * The depending roles for the user are set for the given JAAS system. All
     * roles are added to the loaded roles in the cache of this person.
     *
     * @param _jaasSystem JAAS system for which the roles are set
     * @param _companies set of company to set for the JAAS system
     * @throws EFapsException from calling methods
     */
    public void setCompanies(final JAASSystem _jaasSystem,
                             final Set _companies)
        throws EFapsException
    {

        if (_jaasSystem == null) {
            throw new EFapsException(getClass(), "setRoles.nojaasSystem", getName());
        }
        if (_companies == null) {
            throw new EFapsException(getClass(), "setRoles.noRoles", getName());
        }
        for (final Company role : _companies) {
            add(role);
        }
    }

    /**
     * The method reads directly from the database all stores roles for the this
     * person. The found roles are returned as instance of {@link java.util.Set}
     * .
     *
     * @return set of all found roles for all JAAS systems
     * @see #getRolesFromDB(JAASSystem);
     * @throws EFapsException on error
     */
    public Set getRolesFromDB()
        throws EFapsException
    {
        return getRolesFromDB((JAASSystem) null);
    }

    /**
     * The method reads directly from the database all stores roles for the this
     * person. The found roles are returned as instance of {@link java.util.Set}
     * .
     *
     * @param _jaasSystem JAAS system for which the roles are searched in eFaps
     *            (if value is null, all roles independent from the
     *            related JAAS system are returned)
     * @return set of all found roles for given JAAS system
     * @throws EFapsException on error
     */
    public Set getRolesFromDB(final JAASSystem _jaasSystem)
        throws EFapsException
    {

        final Set ret = new HashSet();
        ConnectionResource rsrc = null;
        try {
            rsrc = Context.getThreadContext().getConnectionResource();

            Statement stmt = null;

            try {
                final StringBuilder cmd = new StringBuilder();
                cmd.append("select ").append("USERABSTRACTTO ").append("from V_USERPERSON2ROLE ").append(
                                "where USERABSTRACTFROM=").append(getId());

                if (_jaasSystem != null) {
                    cmd.append(" and JAASSYSID=").append(_jaasSystem.getId());
                }

                stmt = rsrc.getConnection().createStatement();
                final ResultSet resultset = stmt.executeQuery(cmd.toString());
                final Set roleNames = AppAccessHandler.getLoginRoles();
                while (resultset.next()) {
                    final Role role = Role.get(resultset.getLong(1));
                    if (!AppAccessHandler.excludeMode()
                                    || (AppAccessHandler.excludeMode() && roleNames.contains(role.getName()))) {
                        ret.add(role);
                    }
                }
                resultset.close();

            } catch (final SQLException e) {
                throw new EFapsException(getClass(), "getRolesFromDB.SQLException", e, getName());
            } finally {
                try {
                    if (stmt != null) {
                        stmt.close();
                    }
                } catch (final SQLException e) {
                    throw new EFapsException(getClass(), "getRolesFromDB.SQLException", e, getName());
                }
            }
            rsrc.commit();
        } finally {
            if ((rsrc != null) && rsrc.isOpened()) {
                rsrc.abort();
            }
        }
        return ret;
    }

    /**
     * The depending roles for the user are set for the given JAAS system. All
     * roles are added to the loaded roles in the cache of this person.
     *
     * @param _jaasSystem JAAS system for which the roles are set
     * @param _roles set of roles to set for the JAAS system
     * @see #assignRoleInDb
     * @see #unassignRoleInDb
     * @throws EFapsException from calling methods
     */
    public void setRoles(final JAASSystem _jaasSystem,
                         final Set _roles)
        throws EFapsException
    {

        if (_jaasSystem == null) {
            throw new EFapsException(getClass(), "setRoles.nojaasSystem", getName());
        }
        if (_roles == null) {
            throw new EFapsException(getClass(), "setRoles.noRoles", getName());
        }

        for (final Role role : _roles) {
            add(role);
        }

        // current roles
        final Set rolesInDb = getRolesFromDB(_jaasSystem);

        // compare new roles with current roles (add missing roles)
        for (final Role role : _roles) {
            if (!rolesInDb.contains(role)) {
                assignRoleInDb(_jaasSystem, role);
            }
        }

        // compare current roles with new roles (remove roles which are to much)
        for (final Role role : rolesInDb) {
            if (!_roles.contains(role)) {
                unassignRoleInDb(_jaasSystem, role);
            }
        }
    }

    /**
     * For this person, a role is assigned for the given JAAS system.
     *
     * @param _jaasSystem JAAS system for which the role is assigned
     * @param _role role to assign
     * @see AbstractUserObject#assignToUserObjectInDb(Type, JAASSystem,
     *      AbstractUserObject)
     * @throws EFapsException on error
     */
    public void assignRoleInDb(final JAASSystem _jaasSystem,
                               final Role _role)
        throws EFapsException
    {
        assignToUserObjectInDb(CIAdminUser.Person2Role.getType(), _jaasSystem, _role);
    }

    /**
     * The given role is unassigned for the given JAAS system from this person.
     *
     * @param _jaasSystem JAAS system for which the role is assigned
     * @param _role role to unassign
     * @see AbstractUserObject#unassignFromUserObjectInDb(Type, JAASSystem,
     *      AbstractUserObject)
     * @throws EFapsException on error
     */
    public void unassignRoleInDb(final JAASSystem _jaasSystem,
                                 final Role _role)
        throws EFapsException
    {
        unassignFromUserObjectInDb(CIAdminUser.Person2Role.getType(), _jaasSystem, _role);
    }

    /**
     * The method reads directly from eFaps all stored groups for the this
     * person. The found groups are returned as instance of {@link Set}.
     *
     * @param _jaasSystem JAAS system for which the groups must fetched from
     *            eFaps (if value is null, all groups independent
     *            from the related JAAS system are returned)
     * @throws EFapsException on error
     * @return set of all found groups for given JAAS system
     */
    public Set getGroupsFromDB(final JAASSystem _jaasSystem)
        throws EFapsException
    {
        final Set ret = new HashSet();
        ConnectionResource rsrc = null;
        try {
            rsrc = Context.getThreadContext().getConnectionResource();

            Statement stmt = null;

            try {
                final StringBuilder cmd = new StringBuilder();
                cmd.append("select ").append("USERABSTRACTTO ").append("from V_USERPERSON2GROUP ").append(
                                "where USERABSTRACTFROM=").append(getId());

                if (_jaasSystem != null) {
                    cmd.append(" and JAASSYSID=").append(_jaasSystem.getId());
                }

                stmt = rsrc.getConnection().createStatement();
                final ResultSet resultset = stmt.executeQuery(cmd.toString());
                while (resultset.next()) {
                    ret.add(Group.get(resultset.getLong(1)));
                }
                resultset.close();

            } catch (final SQLException e) {
                throw new EFapsException(getClass(), "getGroupsFromDB.SQLException", e, getName());
            } finally {
                try {
                    if (stmt != null) {
                        stmt.close();
                    }
                } catch (final SQLException e) {
                    throw new EFapsException(getClass(), "getGroupsFromDB.SQLException", e, getName());
                }
            }
            rsrc.commit();
        } finally {
            if ((rsrc != null) && rsrc.isOpened()) {
                rsrc.abort();
            }
        }
        return ret;
    }

    /**
     * The depending groups for the user are set for the given JAAS system. All
     * groups are added to the loaded groups in the cache of this person.
     *
     * @param _jaasSystem JAAS system for which the roles are set
     * @param _groups set of groups to set for the JAAS system
     * @see #assignGroupInDb(JAASSystem, Group)
     * @see #unassignGroupInDb(JAASSystem, Group)
     * @throws EFapsException from calling methods
     */
    public void setGroups(final JAASSystem _jaasSystem,
                          final Set _groups)
        throws EFapsException
    {
        if (_jaasSystem == null) {
            throw new EFapsException(getClass(), "setGroups.nojaasSystem", getName());
        }
        if (_groups == null) {
            throw new EFapsException(getClass(), "setGroups.noGroups", getName());
        }

        for (final Group group : _groups) {
            add(group);
        }

        // current groups
        final Set groupsInDb = getGroupsFromDB(_jaasSystem);

        // compare new roles with current groups (add missing groups)
        for (final Group group : _groups) {
            if (!groupsInDb.contains(group)) {
                assignGroupInDb(_jaasSystem, group);
            }
        }

        // compare current roles with new groups (remove groups which are to
        // much)
        for (final Group group : groupsInDb) {
            if (!_groups.contains(group)) {
                unassignGroupInDb(_jaasSystem, group);
            }
        }
    }

    /**
     * For this person, a group is assigned for the given JAAS system.
     *
     * @param _jaasSystem JAAS system for which the group is assigned
     * @param _group group to assign
     * @throws EFapsException on error
     * @see AbstractUserObject#assignToUserObjectInDb
     */
    public void assignGroupInDb(final JAASSystem _jaasSystem,
                                final Group _group)
        throws EFapsException
    {
        assignToUserObjectInDb(CIAdminUser.Person2Group.getType(), _jaasSystem, _group);
    }

    /**
     * The given group is unassigned for the given JAAS system from this person.
     *
     * @param _jaasSystem JAAS system for which the role is assigned
     * @param _group group to unassign
     * @throws EFapsException on error
     * @see AbstractUserObject#unassignFromUserObjectInDb
     */
    public void unassignGroupInDb(final JAASSystem _jaasSystem,
                                  final Group _group)
        throws EFapsException
    {
        unassignFromUserObjectInDb(CIAdminUser.Person2Group.getType(), _jaasSystem, _group);
    }

    /**
     * Update the last login date of this person to current time stamp.
     *
     * @throws EFapsException if the last login information could not be updated
     */
    public void updateLastLogin()
        throws EFapsException
    {
        ConnectionResource rsrc = null;
        try {
            final Context context = Context.getThreadContext();
            rsrc = context.getConnectionResource();

            Statement stmt = null;
            final StringBuilder cmd = new StringBuilder();
            try {

                cmd.append("update T_USERPERSON ").append("set LASTLOGIN=").append(
                                Context.getDbType().getCurrentTimeStamp()).append(", LOGINTRIES=0 ")
                                .append("where ID=").append(getId());
                stmt = rsrc.getConnection().createStatement();
                final int rows = stmt.executeUpdate(cmd.toString());
                if (rows == 0) {
                    Person.LOG.error("could not execute '" + cmd.toString()
                                    + "' to update last login information for person '" + toString() + "'");
                    throw new EFapsException(getClass(), "updateLastLogin.NotUpdated", cmd.toString(), getName());
                }
            } catch (final SQLException e) {
                Person.LOG.error("could not execute '" + cmd.toString()
                                + "' to update last login information for person '" + toString() + "'", e);
                throw new EFapsException(getClass(), "updateLastLogin.SQLException", e, cmd.toString(), getName());
            } finally {
                try {
                    if (stmt != null) {
                        stmt.close();
                    }
                } catch (final SQLException e) {
                    throw new EFapsException(getClass(), "updateLastLogin.SQLException", e, cmd.toString(), getName());
                }
            }
            rsrc.commit();
        } finally {
            if ((rsrc != null) && rsrc.isOpened()) {
                rsrc.abort();
            }
        }
    }

    /**
     * This is the getter method for instance variable {@link #roles}.
     *
     * @return the value of the instance variable {@link #roles}.
     * @see #roles
     */
    public Set getRoles()
    {
        return this.roles;
    }

    /**
     * This is the getter method for instance variable {@link #groups}.
     *
     * @return the value of the instance variable {@link #groups}.
     * @see #groups
     */
    public Set getGroups()
    {
        return this.groups;
    }

    /**
     * Getter method for instance variable {@link #companies}.
     *
     * @return value of instance variable {@link #companies}
     */
    public Set getCompanies()
    {
        return this.companies;
    }

    /**
     * Method to initialize the Cache of this CacheObjectInterface.
     */
    public static void initialize()
    {
        Person.CACHE.initialize(Person.class);
    }

    /**
     * Returns a string representation of this person.
     *
     * @return string representation of this person
     */
    @Override
    public String toString()
    {
        return new ToStringBuilder(this)
                        .appendSuper(super.toString())
                        .append("attrValues", this.attrValues)
                        .append("roles", this.roles)
                        .append("groups", this.groups)
                        .append("companies", this.companies)
                        .toString();
    }

    /**
     * Returns for given parameter _id the instance of class
     * {@link Person}.
     *
     * @param _id id to search in the cache
     * @throws EFapsException on error
     * @return instance of class {@link Person}
     * @see #CACHE
     * @see #getFromDB
     */
    public static Person get(final long _id)
        throws EFapsException
    {
        Person ret = Person.getCache().get(_id);
        if (ret == null) {
            ret = Person.getFromDB("select "
                            + "V_USERPERSON.ID,"
                            + "V_USERPERSON.NAME, "
                            + "STATUS "
                            + "from V_USERPERSON "
                            + "where V_USERPERSON.ID=" + _id);
        }
        return ret;
    }

    /**
     * Returns for given parameter _name the instance of class
     * {@link Person}.
     *
     * @param _name name to search in the cache
     * @throws EFapsException on error
     * @return instance of class {@link Person}
     * @see #CACHE
     * @see #getFromDB
     */
    public static Person get(final String _name)
        throws EFapsException
    {
        Person ret = Person.getCache().get(_name);
        if (ret == null) {
            ret = Person.getFromDB("select "
                            + "V_USERPERSON.ID,"
                            + "V_USERPERSON.NAME, "
                            + "STATUS "
                            + "from V_USERPERSON "
                            + "where V_USERPERSON.NAME='" + _name + "'");
        }
        return ret;
    }

    /**
     * The static method reads with the help of given sql statement the id and
     * name of the person, creates a new person instance, adds the instance to
     * the cache and read all related information for the person instance with
     * {@link #readFromDB}.
     *
     * @param _sql sql statement used to get the person from database
     * @throws EFapsException on error
     * @return person instance with the found values from database
     * @see #get(long)
     * @see #get(String)
     * @see #readFromDB
     */
    private static Person getFromDB(final String _sql)
        throws EFapsException
    {
        Person ret = null;
        ConnectionResource rsrc = null;
        try {
            rsrc = Context.getThreadContext().getConnectionResource();

            Statement stmt = null;

            try {
                stmt = rsrc.getConnection().createStatement();
                final ResultSet resultset = stmt.executeQuery(_sql);
                if (resultset.next()) {
                    final long id = resultset.getLong(1);
                    final String name = resultset.getString(2);
                    final boolean status = resultset.getBoolean(3);
                    ret = new Person(id, name.trim(), status);
                    Person.getCache().addObject(ret);
                }
                resultset.close();
            } catch (final SQLException e) {
                Person.LOG.error("search for person with SQL statement '" + _sql + "' is not possible", e);
                throw new EFapsException(Person.class, "getFromDB.SQLException", e, _sql);
            } finally {
                try {
                    if (stmt != null) {
                        stmt.close();
                    }
                } catch (final SQLException e) {
                    Person.LOG.error("close of SQL statement is not possible", e);
                }
            }
            rsrc.commit();
        } finally {
            if ((rsrc != null) && rsrc.isOpened()) {
                rsrc.abort();
            }
        }
        if (ret != null) {
            ret.readFromDB();
        }
        return ret;
    }

    /**
     * Returns for given parameter _jaasKey the instance of class
     * {@link Person}. The parameter _jaasKey is the name of the person
     * used in the given JAAS system for the person.
     *
     * @param _jaasSystem JAAS system for which the JAAS key is named
     * @param _jaasKey key in the foreign JAAS system for which the person is
     *            searched
     * @throws EFapsException on error
     * @return instance of class {@link Person}, or null if person
     *         is not found
     * @see #get(long)
     */
    public static Person getWithJAASKey(final JAASSystem _jaasSystem,
                                        final String _jaasKey)
        throws EFapsException
    {
        long personId = 0;
        ConnectionResource rsrc = null;
        try {
            rsrc = Context.getThreadContext().getConnectionResource();

            Statement stmt = null;

            try {
                final StringBuilder cmd = new StringBuilder();
                cmd.append("select ").append("ID ").append("from V_USERPERSONJASSKEY ").append("where JAASKEY='")
                                .append(_jaasKey).append("' ").append("and JAASSYSID=").append(_jaasSystem.getId());

                stmt = rsrc.getConnection().createStatement();
                final ResultSet resultset = stmt.executeQuery(cmd.toString());
                if (resultset.next()) {
                    personId = resultset.getLong(1);
                }
                resultset.close();

            } catch (final SQLException e) {
                Person.LOG.error("search for person for JAAS system '" + _jaasSystem.getName() + "' with key '"
                                + _jaasKey + "' is not possible", e);
                throw new EFapsException(Person.class, "getWithJAASKey.SQLException", e, _jaasSystem.getName(),
                                _jaasKey);
            } finally {
                try {
                    if (stmt != null) {
                        stmt.close();
                    }
                } catch (final SQLException e) {
                    throw new EFapsException(Person.class, "getWithJAASKey.SQLException", e, _jaasSystem.getName(),
                                    _jaasKey);
                }
            }
            rsrc.commit();
        } finally {
            if ((rsrc != null) && rsrc.isOpened()) {
                rsrc.abort();
            }
        }
        return Person.get(personId);
    }

    /**
     * @param _jaasSystem JAAS system which want to create a new person in eFaps
     * @param _jaasKey key of the person in the JAAS system
     * @param _userName name in the eFaps system (used as proposal, it's tested
     *            for uniqueness and changed if needed!)
     * @return new created person
     * @throws EFapsException if person could not be created in eFaps
     * @see #assignToJAASSystem
     */
    public static Person createPerson(final JAASSystem _jaasSystem,
                                      final String _jaasKey,
                                      final String _userName)
        throws EFapsException
    {
        long persId = 0;
        final Type persType = CIAdminUser.Person.getType();
        ConnectionResource rsrc = null;
        try {
            final Context context = Context.getThreadContext();

            rsrc = context.getConnectionResource();

            PreparedStatement stmt = null;
            try {
                StringBuilder cmd = new StringBuilder();

                // TODO: check for uniqueness!
                // TODO: hard coded mofifier and creator
                if (Context.getDbType().supportsGetGeneratedKeys()) {
                    cmd.append("insert into ").append(persType.getMainTable().getSqlTable()).append(
                                    "(TYPEID,NAME,CREATOR,CREATED,MODIFIER,MODIFIED) ").append("values (");
                } else {
                    persId = Context.getDbType().getNewId(rsrc.getConnection(), persType.getMainTable().getSqlTable(),
                                    "ID");
                    cmd.append("insert into ").append(persType.getMainTable().getSqlTable()).append(
                                    "(ID,TYPEID,NAME,CREATOR,CREATED,MODIFIER,MODIFIED) ").append("values (").append(
                                    persId).append(",");
                }
                cmd.append(persType.getId()).append(",").append("'").append(_userName).append("',").append(
                                context.getPersonId()).append(",").append(Context.getDbType().getCurrentTimeStamp())
                                .append(",").append(context.getPersonId()).append(",").append(
                                                Context.getDbType().getCurrentTimeStamp()).append(")");

                if (persId == 0) {
                    stmt = rsrc.getConnection().prepareStatement(cmd.toString(), new String[] { "ID" });
                } else {
                    stmt = rsrc.getConnection().prepareStatement(cmd.toString());
                }

                int rows = stmt.executeUpdate();
                if (rows == 0) {
                    Person.LOG.error("could not execute '" + cmd.toString() + "' for JAAS system '"
                                    + _jaasSystem.getName() + "' person with key '" + _jaasKey
                                    + "' and user name '" + _userName + "'");
                    throw new EFapsException(Person.class, "createPerson.NotInserted", cmd.toString(), _jaasSystem
                                    .getName(), _jaasKey, _userName);
                }
                if (persId == 0) {
                    final ResultSet resultset = stmt.getGeneratedKeys();
                    if (resultset.next()) {
                        persId = resultset.getLong(1);
                    }
                }

                stmt.close();

                cmd = new StringBuilder();
                cmd.append("insert into T_USERPERSON").append("(ID,FIRSTNAME,LASTNAME,EMAIL) ").append("values (")
                                .append(persId).append(",'-','-','-')");
                stmt = rsrc.getConnection().prepareStatement(cmd.toString());
                rows = stmt.executeUpdate();
                if (rows == 0) {
                    Person.LOG.error("could not execute '" + cmd.toString() + "' for JAAS system '"
                                    + _jaasSystem.getName()
                                    + "' person with key '" + _jaasKey + "' and user name '" + _userName + "'");
                    throw new EFapsException(Person.class, "createPerson.NotInserted", cmd.toString(), _jaasSystem
                                    .getName(), _jaasKey, _userName);
                }

            } catch (final SQLException e) {
                Person.LOG.error("could not create for JAAS system '" + _jaasSystem.getName() + "' person with key '"
                                + _jaasKey + "' and user name '" + _userName + "'", e);
                throw new EFapsException(Person.class, "createPerson.SQLException", e, _jaasSystem.getName(), _jaasKey,
                                _userName);
            } finally {
                try {
                    if (stmt != null) {
                        stmt.close();
                    }
                } catch (final SQLException e) {
                    throw new EFapsException(Person.class, "createPerson.SQLException", e, _jaasSystem.getName(),
                                    _jaasKey);
                }
            }
            rsrc.commit();
        } finally {
            if ((rsrc != null) && rsrc.isOpened()) {
                rsrc.abort();
            }
        }

        final Person ret = Person.get(persId);
        ret.assignToJAASSystem(_jaasSystem, _jaasKey);
        return ret;
    }

    /**
     * Static getter method for the type hashtable {@link #CACHE}.
     *
     * @return value of static variable {@link #CACHE}
     */
    public static AbstractCache getCache()
    {
        return Person.CACHE;
    }

    /**
     * This Class is used to store a Person in the Cache.
     */
    private static final class PersonCache
        extends AbstractCache
    {
        /**
         * Method is overwritten because the map is stored in the context.
         *
         * @return Map by id
         */
        @Override
        @SuppressWarnings("unchecked")
        public Map getCache4Id()
        {
            Map map = null;
            try {
                map = (PersonMap) Context.getThreadContext().getSessionAttribute(Person.CACHE4IDKEY);
                if (map == null) {
                    map = new PersonMap();
                    Context.getThreadContext().setSessionAttribute(Person.CACHE4IDKEY, map);
                }
            } catch (final EFapsException e) {
                Person.LOG.error("could not read or set a SessionAttribute for " + Person.CACHE4IDKEY, e);
            }
            return map;
        }

        /**
         * Method is overwritten because the map is stored in the context.
         *
         * @return Map by name
         */
        @Override
        @SuppressWarnings("unchecked")
        protected Map getCache4Name()
        {
            Map map = null;
            try {
                map = (Map) Context.getThreadContext().getSessionAttribute(Person.CACHE4NAMEKEY);
                if (map == null) {
                    map = new PersonMap();
                    Context.getThreadContext().setSessionAttribute(Person.CACHE4NAMEKEY, map);
                }
            } catch (final EFapsException e) {
                Person.LOG.error("could not read or set a SessionAttribute for the " + Person.CACHE4NAMEKEY, e);
            }
            return map;
        }

        /**
         * Method is overwritten because the map is stored in the context.
         *
         * {@inheritDoc}
         */
        @Override
        protected void setCache4Id(final Map _cache4Id)
        {
            try {
                Context.getThreadContext().setSessionAttribute(Person.CACHE4IDKEY, _cache4Id);
            } catch (final EFapsException e) {
                Person.LOG.error("could set a SessionAttribute for the " + Person.CACHE4IDKEY, e);
            }
        }

        /**
         * Method is overwritten because the map is stored in the context.
         *
         * {@inheritDoc}
         */
        @Override
        protected void setCache4Name(final Map _cache4Name)
        {
            try {
                Context.getThreadContext().setSessionAttribute(Person.CACHE4NAMEKEY, _cache4Name);
            } catch (final EFapsException e) {
                Person.LOG.error("could not set a SessionAttribute for the " + Person.CACHE4NAMEKEY, e);
            }
        }

        /**
         * {@inheritDoc}
         */
        @Override
        protected Map getNewCache4Id()
        {
            return new PersonMap();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        protected Map getNewCache4Name()
        {
            return new PersonMap();
        }

        /**
         * Method must not be overwritten (used), because the person cache is
         * stored inside the session and is growing dynamically during the
         * session up to a maximum value defined by a SystemAttribute.
         *
         * @param _cache4Id     cache 4 id
         * @param _cache4Name   cache 4 name
         * @param _cache4UUID   not used
         * @throws CacheReloadException on error
         */
        @Override
        protected void readCache(final Map _cache4Id,
                                 final Map _cache4Name,
                                 final Map _cache4UUID)
            throws CacheReloadException
        {
            try {
                Context.getThreadContext().setSessionAttribute(Person.CACHE4NAMEKEY, _cache4Name);
                Context.getThreadContext().setSessionAttribute(Person.CACHE4IDKEY, _cache4Id);
            } catch (final EFapsException e) {
                throw new CacheReloadException("error in reading cache for Person", e);
            }
        }

        /**
         * Implementation of a LinkedHashMap with limited capacity.
         */
        public class PersonMap extends LinkedHashMap
        {
            /**
             * Needed for serialization.
             */
            private static final long serialVersionUID = 1L;

            @Override
            protected boolean removeEldestEntry(final Entry _eldest)
            {
                int size = 0;
                final SystemConfiguration config = EFapsSystemConfiguration.KERNEL.get();
                if (config != null) {
                    try {
                        size = EFapsSystemConfiguration.KERNEL.get().getAttributeValueAsInteger("Cache4PersonMaxSize");
                    } catch (final EFapsException e) {
                       Person.LOG.debug("error", e);
                    }
                }

                if (size < 1) {
                    size = 100;
                }
                return size() > size;
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy