<file_sep>package org.dyndns.fzoli.mill.server.model.dao;

import java.util.Date;
import java.util.List;
import javax.persistence.EntityTransaction;
import javax.persistence.PersistenceException;
import javax.persistence.TypedQuery;
import org.apache.commons.lang.time.DateUtils;
import org.dyndns.fzoli.mill.common.InputValidator;
import org.dyndns.fzoli.mill.common.Permission;
import org.dyndns.fzoli.mill.common.key.PlayerBuilderReturn;
import org.dyndns.fzoli.mill.common.key.PlayerReturn;
import org.dyndns.fzoli.mill.common.model.entity.Sex;
import org.dyndns.fzoli.mill.server.model.entity.Message;
import org.dyndns.fzoli.mill.server.model.entity.Player;

/**
 *
 * @author zoli
 */
public class PlayerDAO extends AbstractObjectDAO {
    
    private static final int MAX_RES = 25;
    private static final boolean DELETE_MESSAGES = true;
    
    public List<Player> getPossibleFriends(Player player) {
        try {
            return getEntityManager().createQuery("SELECT p FROM Player p WHERE :player MEMBER OF p.friendWishList", Player.class).setParameter("player", player).getResultList();
        }
        catch (PersistenceException ex) {
            ex.printStackTrace();
            return null;
        }
    }
    
    public List<Message> getMessages(Player address) {
        try {
            TypedQuery<Message> query = getEntityManager().createQuery("SELECT m FROM Message m WHERE m.address = :address", Message.class);
            return query.setParameter("address", address).getResultList();
        }
        catch (PersistenceException ex) {
            ex.printStackTrace();
            return null;
        }
    }
    
    public boolean removeMessages(Player p1, Player p2) {
        if (p1 == null || p2 == null) return false;
        try {
            List<Message> messages = getEntityManager().createQuery("SELECT m FROM Message m WHERE (m.sender.playerName = :p1 AND m.address.playerName = :p2) OR (m.sender.playerName = :p2 AND m.address.playerName = :p1)", Message.class)
                .setParameter("p1", p1.getPlayerName())
                .setParameter("p2", p2.getPlayerName())
                .getResultList();
            if (DELETE_MESSAGES) {
                EntityTransaction tr = getEntityManager().getTransaction();
                tr.begin();
                getEntityManager().createQuery("DELETE FROM Message m WHERE (m.sender.playerName = :p1 AND m.address.playerName = :p2) OR (m.sender.playerName = :p2 AND m.address.playerName = :p1)")
                        .setParameter("p1", p1.getPlayerName())
                        .setParameter("p2", p2.getPlayerName())
                        .executeUpdate();
                tr.commit();
            }
            for (Message m : messages) {
                Player p = m.getSender();
                p.getPostedMessages().remove(m);
                save(p);
            }
            return true;
        }
        catch (PersistenceException ex) {
            return false;
        }
    }
    
    public List<Player> getPlayers() {
        try {
            return getEntityManager().createQuery("SELECT p FROM Player p", Player.class).getResultList();
        }
        catch (PersistenceException ex) {
            ex.printStackTrace();
            return null;
        }
    }
    
    public int getLastPage(long count) {
        double d = count / (double)MAX_RES;
        if (d != (int)d) d = 1 + (int)d;
        return (int)d;
    }
    
    public long getPlayerCount(Player asker, String names, String age, String sexName, String country, String region, String city) {
        InputValidator.AgeInterval ages = InputValidator.getAges(age);
        return getPlayerCount(asker, names, ages.getFrom(), ages.getTo(), getSex(sexName), country, region, city);
    }
    
    public List<Player> getPlayers(int page, Player asker, String names, String age, String sexName, String country, String region, String city) {
        InputValidator.AgeInterval ages = InputValidator.getAges(age);
        return getPlayers(page, asker, names, ages.getFrom(), ages.getTo(), getSex(sexName), country, region, city);
    }
    
    public long getPlayerCount(Player asker, String names, Integer ageFrom, Integer ageTo, Sex sex, String country, String region, String city) {
        try {
            List<Long> l = getPlayers(Long.class, asker, null, names, ageFrom, ageTo, sex, country, region, city);
            if (l.isEmpty()) return 0;
            return l.get(0);
        }
        catch (Exception ex) {
            ex.printStackTrace();
            return -1;
        }
    }
    
    public List<Player> getPlayers(int page, Player asker, String names, Integer ageFrom, Integer ageTo, Sex sex, String country, String region, String city) {
        return getPlayers(Player.class, asker, page, names, ageFrom, ageTo, sex, country, region, city);
    }
    
    private <T> List<T> getPlayers(Class<T> clazz, Player asker, Integer page, String names, Integer ageFrom, Integer ageTo, Sex sex, String country, String region, String city) {
        if (country == null || country.trim().isEmpty()) country = region = city = null;
        else if (region == null || region.trim().isEmpty()) region = city = null;
             else if (city != null && city.trim().isEmpty()) city = null;
        try {
            return getEntityManager().createQuery("SELECT " + (clazz.equals(Player.class) ? "p" : "count(p)") + " FROM Player p WHERE "
                    + "(:name IS NULL OR :name = '' OR upper(p.playerName) LIKE upper(:name) OR upper(p.personalData.firstName) LIKE upper(:name) OR upper(p.personalData.lastName) LIKE upper(:name)) AND "
                    + "(:dateFrom IS NULL OR :dateTo IS NULL OR p.personalData.birthDate BETWEEN :dateFrom AND :dateTo) AND "
                    + "(:sex IS NULL OR p.personalData.sex = :sex) AND "
                    + "((:country IS NULL OR :country = '' OR p.personalData.country = :country) AND "
                    + "(:region IS NULL OR :region = '' OR p.personalData.region = :region) AND "
                    + "(:city IS NULL OR :city = '' OR p.personalData.city = :city)) AND "
                    + "(p.activePermission NOT MEMBER OF :perms OR (:perm = :root AND NOT p.permission = :root) OR (:shield)) AND "
                    + "(NOT p = :asker)", clazz)
                    .setMaxResults(MAX_RES)
                    .setFirstResult(clazz.equals(Player.class) && page > 0 ? (page - 1) * MAX_RES : 0)
                    .setParameter("name", names)
                    .setParameter("dateFrom", ageTo == null ? null : DateUtils.addYears(new Date(), -1 * ageTo))
                    .setParameter("dateTo", ageFrom == null ? null : DateUtils.addYears(new Date(), -1 * ageFrom))
                    .setParameter("sex", sex)
                    .setParameter("country", country)
                    .setParameter("region", region)
                    .setParameter("city", city)
                    .setParameter("asker", asker)
                    .setParameter("root", Permission.ROOT)
                    .setParameter("shield", asker == null? false : Permission.hasPermission(asker.getPermissionMask(false), Permission.SHIELD_MODE))
                    .setParameter("perm", asker == null ? 0 : asker.getPermissionMask(false))
                    .setParameter("perms", Permission.getMasks(Permission.HIDDEN_MODE))
                    .getResultList();
        }
        catch (PersistenceException ex) {
            ex.printStackTrace();
            return null;
        }
    }
    
    public Player getPlayer(String name) {
        if (name == null) return null;
        try {
            TypedQuery<Player> query = getEntityManager().createQuery("SELECT p FROM Player p WHERE upper(p.playerName) = upper(:name)", Player.class);
            return query.setParameter("name", name).getSingleResult();
        }
        catch (PersistenceException ex) {
            ex.printStackTrace();
            return null;
        }
    }
    
    public PlayerReturn verify(String name, String password, boolean hash) {
        if (name == null || password == null) return PlayerReturn.NULL;
        if (!InputValidator.isUserIdValid(name) || !InputValidator.isPasswordValid(password, hash)) return PlayerReturn.INVALID;
        Player p = getPlayer(name);
        if (p != null) {
            if (!hash) password = InputValidator.md5Hex(password);
            if (password.equals(p.getPassword())) return PlayerReturn.OK;
        }
        return PlayerReturn.NOT_OK;
    }
    
    public boolean isPlayerNameExists(String name) {
        return isExists(name, "playerName");
    }

    public boolean isEmailExists(String email) {
        if (email == null || email != null && email.isEmpty()) return false;
        TypedQuery<Boolean> query = getEntityManager().createQuery("SELECT count(p) > 0 FROM Player p WHERE upper(p.email) = upper(:value) AND p.validated = true", Boolean.class);
        query.setParameter("value", email);
        return isExists(query);
    }

    private boolean isExists(String value, String property) {
        if (value == null || property == null) return false;
        TypedQuery<Boolean> query = getEntityManager().createQuery("SELECT count(p) > 0 FROM Player p WHERE upper(p." + property + ") = upper(:value)", Boolean.class);
        query.setParameter("value", value);
        return isExists(query);
    }
    
    private boolean isExists(TypedQuery<Boolean> query) {
        try {
            return query.getSingleResult();
        }
        catch (PersistenceException ex) {
            return false;
        }
    }
    
    public long getPlayerCount() {
        try {
            return getEntityManager().createQuery("SELECT count(p) FROM Player p", Long.class).getSingleResult();
        }
        catch (PersistenceException ex) {
            return 0;
        }
    }
    
    public PlayerBuilderReturn createPlayer(Player player, boolean hash) {
        if (player == null) {
            return PlayerBuilderReturn.NULL;
        }
        if (!InputValidator.isUserIdValid(player.getPlayerName())) {
            return PlayerBuilderReturn.INVALID_USER;
        }
        if (!hash && !InputValidator.isPasswordValid(player.getPassword())) {
            return PlayerBuilderReturn.INVALID_PASSWORD;
        }
        if (hash && !InputValidator.isPasswordHashValid(player.getPassword())) {
            return PlayerBuilderReturn.INVALID_PASSWORD;
        }
        if (player.getPassword().equals(player.getPlayerName())) {
            return PlayerBuilderReturn.PASSWORD_NOT_USER;
        }
        if (!InputValidator.isEmailValid(player.getEmail())) {
            return PlayerBuilderReturn.INVALID_EMAIL;
        }
        if (isEmailExists(player.getEmail())) {
            return PlayerBuilderReturn.EMAIL_EXISTS;
        }
        if (isPlayerNameExists(player.getPlayerName())) {
            return PlayerBuilderReturn.USER_EXISTS;
        }
        if (!hash) {
            player.setPassword(InputValidator.md5Hex(player.getPassword()));
        }
        player.setEmail(player.getEmail().toLowerCase());
        if (!save(player)) return PlayerBuilderReturn.EXCEPTION;
        return PlayerBuilderReturn.OK;
    }
    
    public boolean save(Player player) {
        return save(player, Player.class);
    }
    
    public boolean save(Message message) {
        return save(message, Message.class);
    }
    
    private Sex getSex(String name) {
        try {
            return Sex.valueOf(name);
        }
        catch (Exception ex) {
            return null;
        }
    }
    
}<file_sep>package org.dyndns.fzoli.mill.server.model.entity;

import java.io.Serializable;
import java.util.Date;
import javax.persistence.*;
import org.dyndns.fzoli.mill.common.model.entity.MessageType;
import org.dyndns.fzoli.mill.common.model.entity.MessageType.SystemMessage;

/**
 *
 * @author zoli
 */
@Entity
public class Message implements Serializable {

    private static final long serialVersionUID = 1L;
    
    @Id
    @GeneratedValue
    private Long id;

    @Temporal(TemporalType.TIMESTAMP)
    private Date sendDate = new Date();

    private String text;
    
    @Enumerated(EnumType.STRING)
    private MessageType type = MessageType.CHAT;
    
    @Enumerated(EnumType.STRING)
    private SystemMessage msg;
    
    @OneToMany(/*fetch=FetchType.LAZY, cascade=CascadeType.ALL, */mappedBy="postedMessages")
    private Player sender;
    
    @ManyToOne
    private Player address;
    
    protected Message() {
    }
    
    public Message(Player address, SystemMessage msg) {
        this(address, null, msg);
        this.type = MessageType.SYSTEM;
    }
    
    public Message(Player address, String text) {
        this(address, text, false);
    }
    
    public Message(Player address, String text, boolean support) {
        this(address, text, null);
        this.type = support ? MessageType.SUPPORT : MessageType.CHAT;
    }
    
    private Message(Player address, String text, SystemMessage msg) {
        this.address = address;
        this.text = text;
        this.msg = msg;
    }
    
    public Long getId() {
        return id;
    }

    public MessageType getType() {
        return type;
    }

    public SystemMessage getSystemMessage() {
        return msg;
    }

    public String getText() {
        return text;
    }

    public Date getSendDate() {
        return sendDate;
    }

    public Player getSender() {
        return sender;
    }

    public Player getAddress() {
        return address;
    }

    public void setType(MessageType type) {
        this.type = type;
    }

    public void setSystemMessage(SystemMessage msg) {
        this.msg = msg;
    }

    public void setText(String text) {
        this.text = text;
    }

    public Message setSender(Player sender) {
        this.sender = sender;
        return this;
    }

    @Override
    public String toString() {
        return super.toString() + '#' + getId();
    }
    
}<file_sep>package org.dyndns.fzoli.mill.common.key;

/**
 *
 * @author zoli
 */
public interface PlayerBuilderKeys {
    
    String REQ_SET_USER = "set_user";
    String REQ_SET_EMAIL = "set_email";
    String REQ_CREATE = "create";
    String REQ_SAFE_CREATE = "safe_create";
    String REQ_VALIDATE = "validate";
    
}<file_sep>package org.dyndns.fzoli.mill.server.model.entity;

import java.io.Serializable;
import javax.persistence.Embeddable;

/**
 *
 * @author zoli
 */
@Embeddable
public class Point implements Serializable {
    
    private static final long serialVersionUID = 1L;
    
    private int x, y;
    
    public Point() {
    }

    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public int getX() {
        return x;
    }

    public int getY() {
        return y;
    }

    public void setX(int x) {
        this.x = x;
    }

    public void setY(int y) {
        this.y = y;
    }
    
}<file_sep>package org.dyndns.fzoli.mill.server.model;

import java.io.File;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.dyndns.fzoli.email.GMailSender;
import org.dyndns.fzoli.location.entity.Location;
import org.dyndns.fzoli.mill.common.InputValidator;
import org.dyndns.fzoli.mill.common.Permission;
import org.dyndns.fzoli.mill.common.key.ModelKeys;
import org.dyndns.fzoli.mill.common.key.PersonalDataType;
import org.dyndns.fzoli.mill.common.key.PlayerKeys;
import org.dyndns.fzoli.mill.common.key.PlayerReturn;
import org.dyndns.fzoli.mill.common.model.entity.BasePlayer;
import org.dyndns.fzoli.mill.common.model.entity.MessageType;
import org.dyndns.fzoli.mill.common.model.entity.OnlineStatus;
import org.dyndns.fzoli.mill.common.model.entity.Sex;
import org.dyndns.fzoli.mill.common.model.pojo.BaseOnlinePojo;
import org.dyndns.fzoli.mill.common.model.pojo.PlayerData;
import org.dyndns.fzoli.mill.common.model.pojo.PlayerEvent;
import org.dyndns.fzoli.mill.server.model.dao.CityDAO;
import org.dyndns.fzoli.mill.server.model.dao.PlayerAvatarDAO;
import org.dyndns.fzoli.mill.server.model.dao.PlayerDAO;
import org.dyndns.fzoli.mill.server.model.dao.ValidatorDAO;
import org.dyndns.fzoli.mill.server.model.entity.ConvertUtil;
import org.dyndns.fzoli.mill.server.model.entity.Message;
import org.dyndns.fzoli.mill.server.model.entity.PersonalData;
import org.dyndns.fzoli.mill.server.model.entity.Player;
import org.dyndns.fzoli.mill.server.model.entity.PlayerAvatar;
import org.dyndns.fzoli.mill.server.servlet.MillControllerServlet;
import org.dyndns.fzoli.mill.server.servlet.ValidatorServlet;
import org.dyndns.fzoli.mvc.common.request.map.RequestMap;
import org.dyndns.fzoli.mvc.server.model.Model;

/**
 *
 * @author zoli
 */
public class PlayerModel extends AbstractOnlineModel<PlayerEvent, PlayerData> implements PlayerKeys {
    
    public static enum SignOutType {
        RESIGN,
        KICK,
        DISCONNECT,
        NORMAL
    }

    private final static CityDAO CDAO = new CityDAO();
    private final static PlayerDAO DAO = new PlayerDAO();
    private final static ValidatorDAO VDAO = new ValidatorDAO();
    private final static PlayerAvatarDAO ADAO = new PlayerAvatarDAO();
    
    private Player player;
    private org.dyndns.fzoli.mill.common.model.entity.Player commonPlayer;

    private final HashMap<Player, PlayerChangeType> changedPlayers = new HashMap<Player, PlayerChangeType>();
    
    @Override
    public Player getPlayer() {
        return player;
    }
    
    @Override
    public void reinitPlayer() {
        if (player != null) {
            player = DAO.getPlayer(player.getPlayerName());
            commonPlayer = ConvertUtil.createPlayer(this);
        }
    }
    
    @Override
    public org.dyndns.fzoli.mill.common.model.entity.Player getCommonPlayer() {
        return commonPlayer;
    }

    public boolean isEmailFree(String email) {
        return !DAO.isEmailExists(email);
    }
    
    public PlayerReturn signIn(String name, String password, boolean hash) {
        PlayerReturn ret = DAO.verify(name, password, hash);
        if (ret == PlayerReturn.OK) {
            if (player != null) signOut(SignOutType.RESIGN);
            player = DAO.getPlayer(name);
            boolean unsuspend = player.isSuspended();
            if (unsuspend) player.setSuspended(false);
            player.updateSignInDate();
            DAO.save(player);
            commonPlayer = ConvertUtil.createPlayer(this);
            if (unsuspend) callOnPlayerChanged(player, PlayerChangeType.UNSUSPEND);
            getModelMap().remove(ModelKeys.PLAYER_BUILDER);
            boolean wasKick = false;
            List<PlayerModel> models = findModels(getKey(), PlayerModel.class);
            for (PlayerModel model : models) {
                String n = model.getPlayerName();
                if (n == null || model == this) continue;
                if (n.equals(name)) { // ha másik sessionben be van már lépve, kijelentkeztetés
                    model.signOut(SignOutType.KICK);
                    wasKick = true;
                    break;
                }
            }
            if (!wasKick) { // bejelentkezés jelzés ha nem volt már bejelentkezve
                callOnPlayerChanged(player, PlayerChangeType.SIGN_IN);
            }
        }
        else {
            try {
                Thread.sleep(2000);
            }
            catch (InterruptedException ex) {
                ;
            }
        }
        return ret;
    }
    
    public PlayerReturn signOut(SignOutType type) {
        if (player == null) return PlayerReturn.NULL;
        if (type != SignOutType.KICK) { // ha nem történt másik sessionben bejelentkezés
            callOnPlayerChanged(player, PlayerChangeType.SIGN_OUT);
        }
        player = null;
        commonPlayer = null;
        if (type == SignOutType.KICK || type == SignOutType.DISCONNECT) { // ha másik sessionben bejelentkeztek vagy megszakadt a klienssel a kapcsolat
            iterateBeanModels(new ModelIterator() {

                @Override
                public void handler(String key, Model model) {
                    if (model instanceof AbstractOnlineModel) { // a session minden online modeljének jelzés a kijelentkeztetésről
                        ((AbstractOnlineModel)model).addEvent(new BaseOnlinePojo(commonPlayer));
                    }
                }
                
            });
        }
        createCaptcha();
        return PlayerReturn.OK;
    }

    private PlayerReturn setActivePermission(int mask) {
        if (player != null && isCaptchaValidated()) {
            player.setActivePermissionMask(mask);
            DAO.save(player);
            commonPlayer = ConvertUtil.createPlayer(this);
            return PlayerReturn.OK;
        }
        return PlayerReturn.NOT_OK;
    }
    
    private PlayerReturn setPlayerState(String state) {
        if (player != null) {
            try {
                OnlineStatus ps = OnlineStatus.valueOf(state);
                OnlineStatus old = player.getOnlineStatus();
                if (!old.equals(ps)) {
                    player.setOnlineStatus(ps);
                    commonPlayer.setOnline(ps.equals(OnlineStatus.ONLINE));
                    DAO.save(player);
                    callOnPlayerChanged(player, commonPlayer.isOnline() ? PlayerChangeType.STATE_ONLINE : PlayerChangeType.STATE_INVISIBLE);
                    return PlayerReturn.OK;
                }
            }
            catch (IllegalArgumentException ex) {
                return PlayerReturn.INVALID;
            }
        }
        return PlayerReturn.NULL;
    }
    
    private PlayerReturn setPassword(String oldPassword, String newPassword, boolean safe) {
        if (!isCaptchaValidated()) return PlayerReturn.NOT_OK;
        if (player == null) return PlayerReturn.NULL;
        if (!InputValidator.isPasswordValid(newPassword, safe) || !InputValidator.isPasswordValid(oldPassword, safe)) return PlayerReturn.INVALID;
        if (!safe) {
            oldPassword = <PASSWORD>.<PASSWORD>(oldPassword);
            newPassword = <PASSWORD>Validator.<PASSWORD>(newPassword);
        }
        if (oldPassword.equals(newPassword)) {
            return PlayerReturn.NO_CHANGE;
        }
        if (oldPassword.equals(player.getPassword())) {
            player.setPassword(newPassword);
            DAO.save(player);
            return PlayerReturn.OK;
        }
        else {
            return PlayerReturn.NOT_OK;
        }
    }
    
    private PlayerReturn setEmail(HttpServletRequest hsr, String password, String email, boolean safe) {
        if (isRequestWrong(password, safe)) return getError(password, safe);
        if (DAO.isEmailExists(email)) return PlayerReturn.EMAIL_NOT_FREE;
        commonPlayer.setEmail(email);
        player.setEmail(email);
        player.setValidated(false);
        DAO.save(player);
        validateEmail(hsr, password, safe);
        return PlayerReturn.OK;
    }
    
    private PlayerReturn validateEmail(HttpServletRequest hsr, String password, boolean safe) {
        if (isRequestWrong(password, safe)) return getError(password, safe);
        return validateEmail(hsr, player);
    }
    
    public PlayerReturn validateEmail(HttpServletRequest hsr, Player player) {
        if (player != null && player.getEmail() != null) {
            if (player.isValidated()) return PlayerReturn.NO_CHANGE;
            if (player.getEmail().isEmpty()) return PlayerReturn.NULL;
            File config = MillControllerServlet.getEmailConfig(hsr);
            try {
                String key = InputValidator.md5Hex(player.getEmail() + new Date().getTime() + Math.random());
                GMailSender.sendEmail(config, player.getEmail(), ValidatorServlet.getEmailValidationSubject(hsr), ValidatorServlet.createValidationEmail(hsr, key, player));
                VDAO.setKey(player, key);
                removeCaptcha();
                List<Player> players = DAO.getPlayers();
                for (Player p : players) {
                    if (!player.getPlayerName().equals(p.getPlayerName()) && player.getEmail().equals(p.getEmail())) p.setEmail("");
                }
                return PlayerReturn.OK;
            }
            catch (Exception ex) {
                ex.printStackTrace();
                return PlayerReturn.ERROR;
            }
        }
        return PlayerReturn.NULL;
    }
    
    private PlayerReturn suspendAccount(String password, boolean safe) {
        if (isRequestWrong(password, safe)) return getError(password, safe);
        if (player.getPermissionMask(false) == Permission.ROOT) return PlayerReturn.NO_CHANGE;
        player.setPersonalData(new PersonalData());
        player.setSuspended(true);
        player.setOnlineStatus(OnlineStatus.ONLINE);
        player.setPermissionMask(0);
        DAO.save(player);
        PlayerAvatar avatar = ADAO.getPlayerAvatar(player.getPlayerName());
        if (avatar != null) {
            avatar.reset();
            ADAO.save(avatar);
        }
        Player tmp = player;
        signOut(SignOutType.KICK);
        callOnPlayerChanged(tmp, PlayerChangeType.SUSPEND);
        return PlayerReturn.OK;
    }
    
    private PlayerReturn setPersonalData(PersonalDataType request, String value) {
        if (!isCaptchaValidated()) return PlayerReturn.NOT_OK;
        if (player == null || request == null) return PlayerReturn.NULL;
        PlayerReturn ret = PlayerReturn.NOT_OK;
        if (request.equals(PersonalDataType.CLEAR)) {
            player.setPersonalData(new PersonalData());
            ret = PlayerReturn.OK;
        }
        else {
            if (value == null) return PlayerReturn.NULL;
        }
        PersonalData data = player.getPersonalData();
        try {
            switch(request) {
                case FIRST_NAME:
                    if (InputValidator.isNameValid(value) && !value.equals(data.getFirstName())) {
                        data.setFirstName(value);
                        ret = PlayerReturn.OK;
                    }
                    break;
                case LAST_NAME:
                    if (InputValidator.isNameValid(value) && !value.equals(data.getLastName())) {
                        data.setLastName(value);
                        ret = PlayerReturn.OK;
                    }
                    break;
                case INVERSE_NAME:
                    boolean b = Boolean.parseBoolean(value);
                    if (b != data.isInverseName()) {
                        data.setInverseName(b);
                        ret = PlayerReturn.OK;
                    }
                    break;
                case BIRTH_DATE:
                    Date date = new Date(Long.parseLong(value));
                    if (InputValidator.isBirthDateValid(date)) {
                        data.setBirthDate(date);
                        ret = PlayerReturn.OK;
                    }
                    break;
                case SEX:
                    Sex s = Sex.valueOf(value);
                    if (!s.equals(data.getSex())) {
                        data.setSex(s);
                        ret = PlayerReturn.OK;
                    }
                    break;
                case COUNTRY:
                    String country = data.getCountry();
                    if ((country == null || !country.equals(value)) && CDAO.getCountryByName(value) != null) {
                        data.setCountry(value);
                        data.setRegion(null);
                        data.setCity(null);
                        ret = PlayerReturn.OK;
                    }
                    break;
                case REGION:
                    String region = data.getRegion();
                    country = data.getCountry();
                    if (country != null && (region == null || !region.equals(value)) && !CDAO.getRegions(country, value).isEmpty()) {
                        data.setRegion(value);
                        data.setCity(null);
                        ret = PlayerReturn.OK;
                    }
                    break;
                case CITY:
                    String city = data.getCity();
                    region = data.getRegion();
                    if (region != null && (city == null || !city.equals(value)) && !CDAO.getCities(data.getCountry(), region, value).isEmpty()) {
                        data.setCity(value);
                        ret = PlayerReturn.OK;
                    }
                    break;
            }
        }
        catch (Exception ex) {
            ex.printStackTrace();
        }
        if (ret.equals(PlayerReturn.OK)) {
            commonPlayer = ConvertUtil.createPlayer(this);
            DAO.save(player);
            callOnPlayerChanged(player, PlayerChangeType.PERSONAL_DATA);
        }
        return ret;
    }
    
    private boolean isRequestWrong(String password, boolean safe) {
        return getError(password, safe) != null;
    }
    
    private PlayerReturn getError(String password, boolean safe) {
        if (!isCaptchaValidated()) return PlayerReturn.NOT_OK;
        if (player == null) return PlayerReturn.NULL;
        if (!InputValidator.isPasswordValid(password, safe)) return PlayerReturn.INVALID;
        if (!safe) password = <PASSWORD>(password);
        if (!password.equals(player.getPassword())) return PlayerReturn.NOT_OK;
        return null;
    }
    
    @Override
    public void onPlayerChanged(Player p, PlayerChangeType type) {
        super.onPlayerChanged(p, type);
        switch (type) {
            case SIGN_IN:
                onSignInOut(p, true, true);
                break;
            case SIGN_OUT:
                onSignInOut(p, false, true);
                break;
            case STATE_INVISIBLE:
                onSignInOut(p, false, false);
                break;
            case STATE_ONLINE:
                if (changedPlayers.containsKey(p)) {
                    synchronized(changedPlayers) {
                        switch (changedPlayers.get(p)) {
                            case PERSONAL_DATA:
                                onPersonalDataChanged(p);
                                break;
                            case AVATAR_CHANGE:
                                onAvatarChange(p);
                                break;
                        }
                        changedPlayers.remove(p);
                    }
                }
                onSignInOut(p, true, false);
                break;
            case SUSPEND:
                onSuspend(p);
                break;
            case UNSUSPEND:
                onUnsuspend(p);
                break;
            case AVATAR_CHANGE:
                onAvatarChange(p);
                break;
            case AVATAR_ENABLE:
                onAvatarEnabled(true);
                break;
            case AVATAR_DISABLE:
                onAvatarEnabled(false);
                break;
            case PERSONAL_DATA:
                onPersonalDataChanged(p);
                break;
        }
    }
    
    public static boolean isEventImportant(Player me, Player p) {
        if (me != null && me != p) {
            if (p.getFriendList().contains(me) && (p.getOnlineStatus().equals(OnlineStatus.ONLINE) || me.canUsePermission(p, Permission.DETECT_INVISIBLE_STATUS))) {
                return true;
            }
        }
        return false;
    }
    
    public void onValidate(boolean add) {
        commonPlayer = ConvertUtil.createPlayer(this);
        addEvent(new PlayerEvent(commonPlayer, add ? PlayerEvent.PlayerEventType.VALIDATE : PlayerEvent.PlayerEventType.INVALIDATE));
    }
    
    private void onPersonalDataChanged(Player p) {
        if (player != p) {
            if (isEventImportant(player, p)) {
                commonPlayer = ConvertUtil.createPlayer(this);
                addEvent(new PlayerEvent(commonPlayer, p.getPlayerName(), PlayerEvent.PlayerEventType.PERSONAL_DATA_CHANGE));
            }
            else {
                synchronized(changedPlayers) {
                    changedPlayers.put(p, PlayerChangeType.PERSONAL_DATA);
                }
            }
        }
    }
    
    private void onAvatarChange(Player p) {
        if (isEventImportant(getPlayer(), p)) {
            addEvent(new PlayerEvent(commonPlayer, p.getPlayerName(), PlayerEvent.PlayerEventType.AVATAR_CHANGE));
        }
        else {
            synchronized(changedPlayers) {
                changedPlayers.put(p, PlayerChangeType.AVATAR_CHANGE);
            }
        }
    }
    
    private void onSuspend(Player p) {
        addSuspendEvent(p, true);
        commonPlayer = ConvertUtil.createPlayer(this);
    }
    
    private void onUnsuspend(Player p) {
        commonPlayer = ConvertUtil.createPlayer(this);
        addSuspendEvent(p, false);
    }
    
    private void onAvatarEnabled(boolean enabled) {
        commonPlayer = ConvertUtil.createPlayer(this);
        addEvent(new PlayerEvent(commonPlayer, enabled ? PlayerEvent.PlayerEventType.AVATAR_ENABLE : PlayerEvent.PlayerEventType.AVATAR_DISABLE));
    }
    
    private void sendSystemMessage(Player sender, MessageType.SystemMessage type) {
        Object model = getModelMap().get(ModelKeys.CHAT, false);
        if (model != null && player != null && sender != null) {
            ((ChatModel) model).sendMessage(new Message(player, type).setSender(sender));
        }
    }
    
    private void onSignInOut(Player p, boolean signIn, boolean sign) {
        if (player != null) {
            boolean canDetect = player.canUsePermission(p, Permission.DETECT_INVISIBLE_STATUS);
            if (sign && p.getOnlineStatus().equals(OnlineStatus.INVISIBLE) && !canDetect) return; //ha be/ki-jelentkezés van és láthatatlan és nincs láthatatlanság detektáló jog, akkor nem kell jelezni
            if (!sign && canDetect) return; // ha állapot váltás történt (tehát nem be/ki-jelentkezés) és van láthatatlanság detektáló jog, nem kell jelezni
            System.out.print("sign " + p.getPlayerName() + " " + (signIn ? "in" : "out") + " detected on session of " + player.getPlayerName() + "...");
            if (canDetect || (p.isFriend(player) && player.isFriend(p))) {
                System.out.println("sent.");
                BasePlayer bp = commonPlayer.findPlayer(p.getPlayerName());
                bp.setOnline(signIn);
                addEvent(new PlayerEvent(commonPlayer, p.getPlayerName(), signIn));
                sendSystemMessage(p, signIn ? MessageType.SystemMessage.SIGN_IN : MessageType.SystemMessage.SIGN_OUT);
            }
            else {
                System.out.println("not sent.");
            }
        }
    }
    
    public void onDisconnect() {
        signOut(SignOutType.DISCONNECT);
    }
    
    private void addSuspendEvent(Player p, boolean suspend) {
        if (findPlayer(p.getPlayerName()) != null) {
            if (!player.canUsePermission(p, Permission.DETECT_SUSPENDED_PLAYER)) {
                addEvent(new PlayerEvent(commonPlayer, p.getPlayerName(), suspend ? PlayerEvent.PlayerEventType.SUSPEND : PlayerEvent.PlayerEventType.UNSUSPEND));
            }
            else {
                addEvent(new PlayerEvent(commonPlayer, p.getPlayerName(), PlayerEvent.PlayerEventType.RELOAD));
            }
        }
    }
    
    private BasePlayer findPlayer(String playerName) {
        if (commonPlayer == null) return null;
        return findPlayer(playerName, commonPlayer.createMergedPlayerList());
    }
    
    private BasePlayer findPlayer(String playerName, List<BasePlayer> l) {
        for (BasePlayer bp : l) {
            if (bp.getPlayerName().equals(playerName)) {
                return bp;
            }
        }
        return null;
    }
    
    private PlayerData.PlayerList findPlayerList(String playerName) {
        if (commonPlayer != null) {
            if (findPlayer(playerName, commonPlayer.getBlockedUserList()) != null) return PlayerData.PlayerList.BLOCKED_PLAYERS;
            if (findPlayer(playerName, commonPlayer.getFriendWishList()) != null) return PlayerData.PlayerList.WISHED_FRIENDS;
            if (findPlayer(playerName, commonPlayer.getPossibleFriends()) != null) return PlayerData.PlayerList.POSSIBLE_FRIENDS;
        }
        return PlayerData.PlayerList.FRIENDS;
    }
    
    @Override
    protected int askModel(HttpServletRequest hsr, RequestMap rm) {
        int ret = super.askModel(hsr, rm);
        String action = rm.getFirst(KEY_REQUEST);
        if (action != null) {
            if (action.equals(REQ_SIGN_OUT)) return signOut(SignOutType.NORMAL).ordinal();
            if (action.equals(REQ_SIGN_IN)) return signIn(rm.getFirst(KEY_USER), rm.getFirst(KEY_PASSWORD), false).ordinal();
            if (action.equals(REQ_SAFE_SIGN_IN)) return signIn(rm.getFirst(KEY_USER), rm.getFirst(KEY_PASSWORD), true).ordinal();
            String value = rm.getFirst(KEY_VALUE);
            if (value != null) {
                if (action.equals(REQ_IS_EMAIL_FREE)) return isEmailFree(value) ? 1 : 0;
            }
            String passwd = rm.getFirst(KEY_PASSWORD);
            if (passwd != null) {
                if (action.equals(REQ_REVALIDATE_EMAIL)) return validateEmail(hsr, passwd, false).ordinal();
                if (action.equals(REQ_SAFE_REVALIDATE_EMAIL)) return validateEmail(hsr, passwd, true).ordinal();
                if (action.equals(REQ_SUSPEND_ACCOUNT)) return suspendAccount(passwd, false).ordinal();
                if (action.equals(REQ_SAFE_SUSPEND_ACCOUNT)) return suspendAccount(passwd, true).ordinal();
            }
        }
        return ret == -1 ? PlayerReturn.NULL.ordinal() : ret;
    }
    
    @Override
    protected int setProperty(HttpServletRequest hsr, RequestMap rm) {
        String action = rm.getFirst(KEY_REQUEST);
        if (action != null) {
            String value = rm.getFirst(KEY_VALUE);
            if (value != null) {
                try {
                    PersonalDataType request = PersonalDataType.valueOf(action);
                    return setPersonalData(request, value).ordinal();
                }
                catch (IllegalArgumentException ex) {
                    ;
                }
                try {
                    if (action.equals(REQ_SET_ACTIVE_PERMISSION)) return setActivePermission(Integer.parseInt(value)).ordinal();
                }
                catch (NumberFormatException ex) {
                    ;
                }
                if (action.equals(REQ_SET_ONLINE_STATUS)) return setPlayerState(value).ordinal();
                String passwd = rm.getFirst(KEY_PASSWORD);
                if (passwd != null) {
                    if (action.equals(REQ_SET_EMAIL)) return setEmail(hsr, passwd, value, false).ordinal();
                    if (action.equals(REQ_SAFE_SET_EMAIL)) return setEmail(hsr, passwd, value, true).ordinal();
                    if (action.equals(REQ_SET_PASSWORD)) return setPassword(passwd, value, false).ordinal();
                    if (action.equals(REQ_SET_SAFE_PASSWORD)) return setPassword(passwd, value, true).ordinal();
                }
            }
        }
        return PlayerReturn.NULL.ordinal(); 
    }

    @Override
    protected PlayerData getProperties(HttpServletRequest hsr, RequestMap rm) {
        String user = rm.getFirst(KEY_USER);
        if (user != null && player != null) {
            if (user.equals(player.getPlayerName())) return new PlayerData(commonPlayer, null);
            else return new PlayerData(findPlayer(user), findPlayerList(user));
        }
        String value = rm.getFirst(KEY_VALUE);
        String action = rm.getFirst(KEY_REQUEST);
        if (player != null && value != null && action != null) {
            PersonalData data = player.getPersonalData();
            if (action.equals(REQ_GET_COUNTRIES)) return new PlayerData(createList(CDAO.findCountriesByName(value)));
            if (action.equals(REQ_GET_REGIONS)) return new PlayerData(createList(CDAO.findRegions(data.getCountry(), value)));
            if (action.equals(REQ_GET_CITIES)) return new PlayerData(createList(CDAO.findCities(data.getCountry(), data.getRegion(), value)));
        }
        return new PlayerData(commonPlayer, isCaptchaValidated(), getCaptchaWidth());
    }
    
    public static List<String> createList(List<? extends Location> l) {
        List<String> ls = new ArrayList<String>();
        for (Location o : l) {
            ls.add(o.getDisplay());
        }
        return ls;
    }
    
}<file_sep>package org.dyndns.fzoli.mill.common.model.pojo;

import org.dyndns.fzoli.mill.common.model.entity.Player;

/**
 *
 * @author zoli
 */
public class PlayerEvent extends BaseOnlinePojo {

    public enum PlayerEventType {
        COMMON,
        SIGNIN,
        SIGNOUT,
        VALIDATE,
        INVALIDATE,
        SUSPEND,
        UNSUSPEND,
        RELOAD,
        AVATAR_CHANGE,
        AVATAR_ENABLE,
        AVATAR_DISABLE,
        PERSONAL_DATA_CHANGE
    }
    
    private PlayerEventType type;
    private String changedPlayer;
    
    public PlayerEvent(Player player) {
        super(player);
        type = PlayerEventType.COMMON;
    }

    public PlayerEvent(Player player, PlayerEventType type) {
        super(player);
        this.type = type;
    }
    
    public PlayerEvent(Player player, String changedPlayer, PlayerEventType type) {
        super(player);
        this.changedPlayer = changedPlayer;
        this.type = type;
    }
    
    public PlayerEvent(Player player, String changedPlayer, boolean signIn) {
        this(player, changedPlayer, signIn ? PlayerEventType.SIGNIN : PlayerEventType.SIGNOUT);
    }

    public PlayerEventType getType() {
        return type;
    }

    public String getChangedPlayer() {
        return changedPlayer;
    }
    
}<file_sep>package org.dyndns.fzoli.mill.server.model.dao;

/**
 *
 * @author zoli
 */
public class CityDAO extends org.dyndns.fzoli.location.dao.CityDAO {

    @Override
    protected String getLocation() {
        return "tcp://localhost/~/cities";
    }
    
}<file_sep>package org.dyndns.fzoli.mill.server.model;

import javax.servlet.http.HttpServletRequest;
import org.dyndns.fzoli.mill.common.key.PlayerRegistryKeys;
import org.dyndns.fzoli.mill.common.model.pojo.PlayerRegistryData;
import org.dyndns.fzoli.mill.common.model.pojo.PlayerRegistryEvent;
import static org.dyndns.fzoli.mill.server.model.PlayerModel.createList;
import org.dyndns.fzoli.mill.server.model.dao.CityDAO;
import org.dyndns.fzoli.mill.server.model.dao.PlayerDAO;
import org.dyndns.fzoli.mill.server.model.entity.ConvertUtil;
import org.dyndns.fzoli.mvc.common.request.map.RequestMap;

/**
 *
 * @author zoli
 */
public class PlayerRegistryModel extends AbstractOnlineModel<PlayerRegistryEvent, PlayerRegistryData> implements PlayerRegistryKeys {
    
    private final static PlayerDAO DAO = new PlayerDAO();
    private final static CityDAO CDAO = new CityDAO();
    
    private int page = 1;
    private String names, ages, sexName, country, region, city;
    
    private void setPage(String action, String value) {
        if (action.equals(REQ_GET_PAGE) && value != null) {
            try {
                page = Integer.parseInt(value);
            }
            catch (NumberFormatException ex) {
                page = 1;
            }
        }
        if (action.equals(REQ_PREV_PAGE)) {
            page--;
        }
        if (action.equals(REQ_NEXT_PAGE)) {
            page++;
        }
    }
    
    private PlayerRegistryData findPlayers() {
        long count = DAO.getPlayerCount(getPlayer(), names, ages, sexName, country, region, city);
        int lastPage = DAO.getLastPage(count);
        if (page < 1) page = 1;
        if (page > lastPage) page = lastPage;
        return new PlayerRegistryData(ConvertUtil.createPlayerList(this, DAO.getPlayers(page, getPlayer(), names, ages, sexName, country, region, city)), count, page, lastPage);
    }
    
    @Override
    protected PlayerRegistryData getProperties(HttpServletRequest hsr, RequestMap rm) {
        String action = rm.getFirst(KEY_REQUEST);
        if (action != null) {
            String value = rm.getFirst(KEY_VALUE);
            if (action.equals(REQ_NEXT_PAGE) || action.equals(REQ_PREV_PAGE) || action.equals(REQ_GET_PAGE)) {
                setPage(action, value);
                return findPlayers();
            }
            if (value != null) {
                if (action.equals(REQ_GET_COUNTRIES)) return new PlayerRegistryData(createList(CDAO.findCountriesByName(value)));
                if (action.equals(REQ_GET_REGIONS)) return new PlayerRegistryData(createList(CDAO.findRegions(country, value)));
                if (action.equals(REQ_GET_CITIES)) return new PlayerRegistryData(createList(CDAO.findCities(country, region, value)));
            }
        }
        return new PlayerRegistryData(getPlayerName(), names, ages, sexName, country, region, city);
    }
    
    @Override
    protected int setProperty(HttpServletRequest hsr, RequestMap rm) {
        String action = rm.getFirst(KEY_REQUEST);
        if (action != null) {
            if (action.equals(REQ_SET_FILTER)) {
                names = rm.getFirst(KEY_NAMES);
                ages = rm.getFirst(KEY_AGES);
                sexName = rm.getFirst(KEY_SEX);
                country = rm.getFirst(KEY_COUNTRY);
                region = rm.getFirst(KEY_REGION);
                city = rm.getFirst(KEY_CITY);
                return 1;
            }
        }
        return 0;
    }
    
}<file_sep>package org.dyndns.fzoli.mill.server.test.objectdb;

import java.util.Arrays;
import java.util.Collection;
import javax.persistence.EntityManager;
import static org.dyndns.fzoli.mill.server.test.objectdb.Util.*;
import org.junit.AfterClass;
import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;

/**
 * ObjectDB first test.
 * 1. Open database connection and clear database.
 * 2. Creating players.
 * 3. a) Add player2 to player1's list.
 *    b) Add player1 to player2's list.
 * 4. Recreate database connection.
 * 5. Try read players.
 * 6. Close database connection.
 * @author zoli
 */
@RunWith(value = Parameterized.class)
public class FirstTest {
    
    private static EntityManager db;
    
    private boolean inverseAdd;
    
    public FirstTest(boolean inverseAdd) {
        this.inverseAdd = inverseAdd;
    }

    @Parameters
    public static Collection<Boolean[]> data() {
        Boolean[][] data = new Boolean[][] { { false }, { true } };
        return Arrays.asList(data);
    }
    
    @BeforeClass
    public static void openDatabase() throws Exception {
        System.out.println("Open database connection.");
        db = createEntityManager();
    }
    
    @Before
    public void initTest() throws Exception {
        System.out.println("Clear database.");
        clearDatabase(db);
    }
    
    @AfterClass
    public static void closeDatabase() throws Exception {
        System.out.println("Close database connection.");
        db.close();
    }
    
    @Test(timeout=10000)
    public void testOne() {
        
        System.out.println("Creating players.");
        Player p1 = new Player(PLAYER1);
        Player p2 = new Player(PLAYER2);
        assertTrue(save(db, p1));
        assertTrue(save(db, p2));
        
        System.out.println("Add player2 to player1's list.");
        p1.getFriendWishList().add(p2);
        assertTrue(save(db, p1));
        if (inverseAdd) {
            System.out.println("Add player1 to player2's list.");
            p2.getFriendWishList().add(p1);
            assertTrue(save(db, p2));
        }
        
        System.out.println("Recreate database connection.");
        db.close();
        db = createEntityManager();
        
        System.out.println("Try read players...");
        System.out.println("Read " + PLAYER1 + '.');
        assertNotNull(getPlayer(db, PLAYER1));
        System.out.println("Read " + PLAYER2 + '.');
        assertNotNull(getPlayer(db, PLAYER2));
        
    }
    
}<file_sep>package org.dyndns.fzoli.mill.client.model;

import java.util.Date;
import org.dyndns.fzoli.mill.common.DateUtil;
import org.dyndns.fzoli.mill.common.key.ChatKeys;
import org.dyndns.fzoli.mill.common.key.ModelKeys;
import org.dyndns.fzoli.mill.common.model.pojo.ChatData;
import org.dyndns.fzoli.mill.common.model.pojo.ChatEvent;
import org.dyndns.fzoli.mvc.client.connection.Connection;
import org.dyndns.fzoli.mvc.client.event.ModelActionListener;
import org.dyndns.fzoli.mvc.common.request.map.RequestMap;

/**
 *
 * @author zoli
 */
public class ChatModel extends AbstractOnlineModel<ChatEvent, ChatData> implements ChatKeys {
    
    public ChatModel(Connection<Object, Object> connection) {
        super(connection, ModelKeys.CHAT, ChatEvent.class, ChatData.class);
        getCache().setSync(getSync());
    }
    
    public int getSync() {
        try {
            return askModel(new RequestMap().setFirst(KEY_REQUEST, REQ_SYNC).setFirst(KEY_DATE, Long.toString(DateUtil.getDateInTimeZone(new Date(), "GMT").getTime())));
        }
        catch (Exception ex) {
            return 0;
        }
    }
    
    public void loadUnreadedMessages(String playerName, ModelActionListener<ChatData> callback) {
        loadMessages(playerName, null, callback);
    }
    
    public void loadMessages(String playerName, Date startDate, ModelActionListener<ChatData> callback) {
        RequestMap map = new RequestMap().setFirst(KEY_REQUEST, REQ_GET_MESSAGES).setFirst(KEY_PLAYER, playerName);
        if (startDate != null) map.setFirst(KEY_DATE, Long.toString(startDate.getTime()));
        getProperties(map, callback);
    }
    
    public void removeMessages(String playerName, ModelActionListener<Integer> callback) {
        askModel(new RequestMap().setFirst(KEY_REQUEST, REQ_REMOVE_MESSAGES).setFirst(KEY_PLAYER, playerName), callback);
    }
    
    public void updateReadDate(String playerName, ModelActionListener<Integer> callback) {
        askModel(new RequestMap().setFirst(KEY_REQUEST, REQ_UPDATE_READ_DATE).setFirst(KEY_PLAYER, playerName), callback);
    }
    
    public void sendMessage(String playerName, String text, ModelActionListener<Integer> callback) {
        askModel(new RequestMap().setFirst(KEY_REQUEST, REQ_SEND_MESSAGE).setFirst(KEY_PLAYER, playerName).setFirst(KEY_VALUE, text), callback);
    }
    
}<file_sep>package org.dyndns.fzoli.mill.server.model.entity;

import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToOne;

/**
 *
 * @author zoli
 */
@Entity
public class Validator implements Serializable {
    
    private static final long serialVersionUID = 1L;
    
    @Id
    @GeneratedValue
    private Long id;
    
    @OneToOne
    private Player player;
    
    private String validatorKey;

    public Validator() {
    }

    public Validator(Player player, String validatorKey) {
        this.player = player;
        this.validatorKey = validatorKey;
    }

    public Player getPlayer() {
        return player;
    }

    public String getValidatorKey() {
        return validatorKey;
    }

    public void setPlayer(Player player) {
        this.player = player;
    }

    public void setValidatorKey(String validatorKey) {
        this.validatorKey = validatorKey;
    }
    
}<file_sep>package org.dyndns.fzoli.mill.common.model.entity;

import java.util.Date;

/**
 *
 * @author zoli
 */
public class PersonalData {
    
    private String firstName, lastName, country, region, city;
    private boolean inverseName;
    private Date birthDate;
    protected Long birth;
    private Sex sex;

    public PersonalData(String firstName, String lastName, boolean inverseName, Date birthDate, Sex sex, String country, String region, String city) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.country = country;
        this.region = region;
        this.city = city;
        this.inverseName = inverseName;
        this.birthDate = birthDate;
        this.sex = sex;
        if (birthDate != null) birth = birthDate.getTime();
    }

    public String getName() {
        return getFirstName() == null || getLastName() == null ? null :
               (!isInverseName() ? getFirstName() + " " + getLastName() : getLastName() + " " + getFirstName()).trim();
    }
    
    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
    
    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getCountry() {
        return country;
    }

    public void setCountry(String country) {
        this.country = country;
    }

    public String getRegion() {
        return region;
    }

    public void setRegion(String region) {
        this.region = region;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public boolean isInverseName() {
        return inverseName;
    }

    public void setInverseName(boolean inverseName) {
        this.inverseName = inverseName;
    }

    public Date getBirthDate() {
        return birthDate;
    }

    public void setBirthDate(Date birthDate) {
        this.birthDate = birthDate;
        if (birthDate != null) this.birth = birthDate.getTime();
    }

    public Sex getSex() {
        return sex;
    }

    public void setSex(Sex sex) {
        this.sex = sex;
    }
    
    public void clear() {
        setFirstName(null);
        setLastName(null);
        setInverseName(false);
        setSex(null);
        setBirthDate(null);
        setCity(null);
	setRegion(null);
        setCountry(null);
    }
    
}<file_sep>package org.dyndns.fzoli.mill.common.model.entity;

/**
 *
 * @author zoli
 */
public enum OnlineStatus {
    ONLINE,
    INVISIBLE
}<file_sep>package org.dyndns.fzoli.mill.server.model.entity;

import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.Serializable;
import javax.imageio.ImageIO;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

/**
 *
 * @author zoli
 */
@Entity
public class PlayerAvatar implements Serializable {
    
    private static final long serialVersionUID = 1L;
    
    @Id
    @GeneratedValue
    private Long id;
    
    private String playerName;
    private byte[] avatar;
    
    private int scale;
    
    @Embedded
    private Point topLeftPoint;

    public PlayerAvatar() {
    }

    public PlayerAvatar(String playerName, BufferedImage avatar, Point topLeftPoint, int scale) {
        this(playerName, imageToByteArray(avatar), topLeftPoint, scale);
    }
    
    public PlayerAvatar(String playerName, byte[] avatar, Point topLeftPoint, int scale) {
        this.playerName = playerName;
        this.avatar = avatar;
        this.scale = scale;
        this.topLeftPoint = topLeftPoint;
    }

    public Long getId() {
        return id;
    }

    public String getPlayerName() {
        return playerName;
    }

    public byte[] getAvatarArray() {
        return avatar;
    }

    public BufferedImage createAvatarImage() {
        try {
            Point p = getTopLeftPoint();
            return getAvatarImage().getSubimage(p.getX(), p.getY(), getScale(), getScale());
        }
        catch (Exception ex) {
            return null;
        }
    }
    
    public BufferedImage getAvatarImage() {
        return byteArrayToImage(getAvatarArray());
    }
    
    public Point getTopLeftPoint() {
        return topLeftPoint;
    }

    public int getScale() {
        return scale;
    }

    public void setAvatar(byte[] avatar) {
        this.avatar = avatar;
    }

    public void setAvatar(BufferedImage avatar) {
        this.avatar = imageToByteArray(avatar);
    }
    
    public void setTopLeftPoint(Point topLeftPoint) {
        this.topLeftPoint = topLeftPoint;
    }

    public void setScale(int scale) {
        this.scale = scale;
    }
    
    public void reset() {
        this.avatar = null;
        this.topLeftPoint = null;
        this.scale = 0;
    }
    
    public static byte[] imageToByteArray(BufferedImage img) {
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        try {
            ImageIO.write(img, "png", os);
        }
        catch (IOException ex) {
            return null;
        }
        return os.toByteArray();
    }
    
    public static BufferedImage byteArrayToImage(byte[] array) {
        try {
            return ImageIO.read(new ByteArrayInputStream(array));
        }
        catch (IOException ex) {
            return null;
        }
    }
    
}<file_sep>package org.dyndns.fzoli.mill.server.model;

import java.util.Date;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.dyndns.fzoli.mill.common.InputValidator;
import org.dyndns.fzoli.mill.common.key.PlayerBuilderKeys;
import org.dyndns.fzoli.mill.common.key.PlayerBuilderReturn;
import org.dyndns.fzoli.mill.common.model.pojo.PlayerBuilderData;
import org.dyndns.fzoli.mill.common.model.pojo.PlayerBuilderEvent;
import org.dyndns.fzoli.mill.server.model.dao.PlayerDAO;
import org.dyndns.fzoli.mill.server.model.entity.Player;
import org.dyndns.fzoli.mvc.common.request.map.RequestMap;

/**
 *
 * @author zoli
 */
public class PlayerBuilderModel extends AbstractMillModel<PlayerBuilderEvent, PlayerBuilderData> implements PlayerBuilderKeys {
    
    private final static int TIMEOUT = 600000; // 10 minutes
    private long initTime = new Date().getTime();
    private String user = "", email = "";
    
    private final static PlayerDAO DAO = new PlayerDAO();
    
    public List<PlayerBuilderModel> findModels() {
        return findModels(getKey(), PlayerBuilderModel.class);
    }
    
    public boolean isTimeout(long time) {
        return time - initTime >= TIMEOUT;
    }
    
    public boolean isUserFree(String user) {
        if (user == null) return true;
        if (DAO.isPlayerNameExists(user)) return false;
        List<PlayerBuilderModel> models = findModels();
        long time = new Date().getTime();
        for (PlayerBuilderModel model : models) {
            if (!model.isTimeout(time) && user.equals(model.user)) return false;
        }
        return true;
    }
    
    public boolean isEmailFree(String email) {
        return !DAO.isEmailExists(email);
    }

    public PlayerBuilderReturn setUser(String user) {
        if (user.equals(this.user)) return PlayerBuilderReturn.OK;
        if (InputValidator.isUserIdValid(user)) {
            if (!isUserFree(user)) return PlayerBuilderReturn.USER_EXISTS;
            this.user = user;
            return PlayerBuilderReturn.OK;
        }
        return PlayerBuilderReturn.INVALID_USER;
    }
    
    public PlayerBuilderReturn setEmail(String email) {
        if (email.equals(this.email)) return PlayerBuilderReturn.OK;
        if (InputValidator.isEmailValid(email)) {
            if (!isEmailFree(email)) return PlayerBuilderReturn.EMAIL_EXISTS;
            this.email = email;
            return PlayerBuilderReturn.OK;
        }
        return PlayerBuilderReturn.INVALID_EMAIL;
    }
    
    public PlayerBuilderReturn createUser(HttpServletRequest sr, String password, boolean hash) {
        if (!isCaptchaValidated()) return PlayerBuilderReturn.WRONG_CAPTCHA;
        if (validate(false)) return PlayerBuilderReturn.VALIDATED;
        if (InputValidator.isPasswordValid(password, hash) && InputValidator.isUserIdValid(user) && InputValidator.isEmailValid(email)) {
            Player player = new Player(user, password, email);
            PlayerBuilderReturn ret = DAO.createPlayer(player, hash);
            if (ret == PlayerBuilderReturn.OK) {
                addStaticEvent();
                validate(true);
                removeCaptcha();
                getPlayerModel(true).signIn(user, password, hash);
                getPlayerModel().validateEmail(sr, player);
            }
            return ret;
        }
        return PlayerBuilderReturn.INVALID;
    }
    
    @Override
    protected int askModel(HttpServletRequest sr, RequestMap m) {
        String action = m.getFirst(KEY_REQUEST);
        String value = m.getFirst(KEY_VALUE);
        if (action != null) {
            if (action.equals(REQ_VALIDATE)) return (validate(true) ? PlayerBuilderReturn.VALIDATED : PlayerBuilderReturn.NOT_VALIDATED).ordinal();
            if (action.equals(REQ_CREATE)) return createUser(sr, value, false).ordinal();
            if (action.equals(REQ_SAFE_CREATE)) return createUser(sr, value, true).ordinal();
        }
        return super.askModel(sr, m);
    }
    
    @Override
    protected PlayerBuilderData getProperties(HttpServletRequest sr, RequestMap m) {
        validate(false);
        return new PlayerBuilderData(user, email, new Date().getTime(), initTime, TIMEOUT, DAO.getPlayerCount(), isCaptchaValidated(), getCaptchaWidth());
    }

    @Override
    protected int setProperty(HttpServletRequest hsr, RequestMap rm) {
        String action = rm.getFirst(KEY_REQUEST);
        String value = rm.getFirst(KEY_VALUE);
        if (action != null && value != null) {
            if (action.equals(REQ_SET_USER)) return setUser(value).ordinal();
            if (action.equals(REQ_SET_EMAIL)) return setEmail(value).ordinal();
        }
        return PlayerBuilderReturn.NULL.ordinal();
    }
    
    private boolean validate(boolean force) {
        long time = new Date().getTime();
        if (force || isTimeout(time)) {
            user = "";
            email = "";
            initTime = time;
            addEvent(true);
            return true;
        }
        return false;
    }
    
    private void addEvent(boolean reset) {
        addEvent(new PlayerBuilderEvent(reset, DAO.getPlayerCount()));
    }

    private void addStaticEvent() {
        addStaticEvent(new PlayerBuilderEvent(false, DAO.getPlayerCount()));
    }
    
}<file_sep>package org.dyndns.fzoli.mill.common.model.pojo;

import org.dyndns.fzoli.mill.common.model.entity.Message;

/**
 *
 * @author zoli
 */
public class ChatEvent extends BaseOnlinePojo {

    private Message message;
    private String clearPlayer;
    private Boolean clear;
    
    public ChatEvent(String playerName) {
        super(playerName);
    }
    
    public ChatEvent(String playerName, String clearPlayer) {
        super(playerName);
        this.clear = true;
        this.clearPlayer = clearPlayer;
    }

    public ChatEvent(String playerName, Message message) {
        super(playerName);
        this.message = message;
    }

    public boolean isClear() {
        if (clear == null) return false;
        return clear;
    }

    public String getClearPlayer() {
        return clearPlayer;
    }
    
    public Message getMessage() {
        return message;
    }
    
}
<file_sep>package org.dyndns.fzoli.mill.server.servlet;

import java.io.IOException;
import java.util.Date;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.dyndns.fzoli.mill.common.key.MillServletURL;
import org.dyndns.fzoli.mill.common.key.ModelKeys;
import org.dyndns.fzoli.mill.server.model.PlayerModel;
import org.dyndns.fzoli.mvc.server.model.bean.ModelBean;
import org.dyndns.fzoli.mvc.server.model.bean.ModelBeanRegister;
import org.dyndns.fzoli.mvc.server.servlet.listener.JSONListenerServlet;

/**
 *
 * @author zoli
 */
@WebServlet(
        urlPatterns={MillServletURL.LISTENER}, 
        initParams ={
            @WebInitParam(name=MillListenerServlet.PARAM_EVENT_DELAY, value="50"),
            @WebInitParam(name=MillListenerServlet.PARAM_EVENT_TIMEOUT, value="20000"),
            @WebInitParam(name=MillListenerServlet.PARAM_GC_DELAY, value="60000")
        }
)
public final class MillListenerServlet extends JSONListenerServlet {

    private static Date lastTime;
    
    @Override
    protected void printResponse(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        final Date now = new Date();
        if (lastTime == null) lastTime = now;
        if (new Date().getTime() - lastTime.getTime() >= 60000) {
            new Thread(new Runnable() {

                @Override
                public void run() {
                    List<ModelBean> beans = ModelBeanRegister.getModelBeans();
                    for (ModelBean bean : beans) {
                        HttpSession s = bean.getSession();
                        if (s == null) continue;
                        try {
                            if (now.getTime() - s.getLastAccessedTime() >= getServletUtils().getEventTimeout() + getServletUtils().getReconnectWait()) {
                                PlayerModel m = (PlayerModel) bean.getModel(ModelKeys.PLAYER);
                                if (m == null) continue;
                                m.onDisconnect();
                            }
                        }
                        catch (Exception ex) {
                            continue;
                        }
                    }
                }
                
            }).start();
            lastTime = now;
        }
        super.printResponse(request, response);
    }
    
}<file_sep>package org.dyndns.fzoli.mill.server.model.dao;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Date;
import java.util.List;
import javax.persistence.EntityTransaction;
import org.dyndns.fzoli.mill.server.model.entity.City;
import org.dyndns.fzoli.mill.server.model.entity.Country;
import org.dyndns.fzoli.mill.server.model.entity.Region;

/**
 *
 * @author zoli
 */
public class City2DAO extends AbstractObjectDAO {

    @Override
    protected String getPath() {
        return "cities.odb";
    }

    public boolean save(Country country) {
        return save(country, Country.class);
    }

    public boolean save(Region region) {
        return save(region, Region.class);
    }

    public boolean save(City city) {
        return save(city, City.class);
    }

    private List<Region> getRegions() {
        return getEntityManager().createQuery("SELECT o FROM Region o", Region.class).getResultList();
    }

    private static String createSql(long from, int count) {
        return "SELECT city.name, city.accent_name, city.latitude, city.longitude, region_code FROM city LEFT JOIN region ON (region.id = city.region) LIMIT " + from + ", " + count;
    }

    private static Region find(List<Region> rs, String code) {
        for (Region r : rs) {
            if (r.getRegionCode().equals(code)) {
                return r;
            }
        }
        return null;
    }

    private static int count = 3000000;
    
    private static void load(long from, Statement s, List<Region> regions, City2DAO dao2) throws SQLException {
        System.out.print(new Date() + ": read from " + from + " .");
        ResultSet result = s.executeQuery(createSql(from, count));
        dao2.getEntityManager().getTransaction().begin();
        System.out.print(".");
        while (result.next()) {
            Region region = find(regions, result.getString("REGION_CODE"));
            City c = new City(region, result.getString("NAME"), result.getString("ACCENT_NAME"), result.getDouble("LATITUDE"), result.getDouble("LONGITUDE"));
            dao2.getEntityManager().persist(c);
        }
        System.out.print(".");
        dao2.getEntityManager().getTransaction().commit();
        result.close();
        System.out.println(" done " + (int)((from / 2533120.0) * 100) + " %");
        load(from + count, s, regions, dao2);
    }

    private static void prepare(CityDAO dao1, City2DAO dao2) {
        List<org.dyndns.fzoli.mill.common.model.entity.Country> countries = dao1.getCountries();
        int i = 0;
        int countriesSize = countries.size();
        for (org.dyndns.fzoli.mill.common.model.entity.Country country : countries) {
            Country c = new Country(country.getID(), country.getName());
            dao2.save(c);
            List<org.dyndns.fzoli.mill.common.model.entity.Region> regions = dao1.getRegionsByCountry(c.getId());
            for (org.dyndns.fzoli.mill.common.model.entity.Region region : regions) {
                Region r = new Region(c, region.getRegionCode(), region.getName());
                dao2.save(r);
            }
            i++;
            System.out.println("country "+i+" / "+countriesSize);
        }
    }
    
    private static void fill(CityDAO dao1, City2DAO dao2) throws SQLException {
        System.out.println("started");
        List<Region> regions = dao2.getRegions();
        Statement s = dao1.getConnection().createStatement();
        System.out.println("connected");
        long start = 0;
        try {
            start = dao2.getEntityManager().createQuery("select count(c) from City c", Long.class).getSingleResult();
        }
        catch (Exception ex) {
            ;
        }
        start += 1011800;
        start += 1010000;
        load(start, s, regions, dao2);
        System.out.println("finished");
    }
    
    public static void main(String[] args) throws SQLException {
        CityDAO dao1 = new CityDAO();
        City2DAO dao2 = new City2DAO();
//        prepare(dao1, dao2);
        fill(dao1, dao2);
    }
    
}<file_sep>package org.dyndns.fzoli.mill.client.model;

import java.util.List;
import org.dyndns.fzoli.mill.common.key.ModelKeys;
import org.dyndns.fzoli.mill.common.key.PersonalDataType;
import org.dyndns.fzoli.mill.common.key.PlayerKeys;
import org.dyndns.fzoli.mill.common.model.entity.BasePlayer;
import org.dyndns.fzoli.mill.common.model.entity.OnlineStatus;
import org.dyndns.fzoli.mill.common.model.pojo.PlayerData;
import org.dyndns.fzoli.mill.common.model.pojo.PlayerEvent;
import org.dyndns.fzoli.mvc.client.connection.Connection;
import org.dyndns.fzoli.mvc.client.event.ModelActionListener;
import org.dyndns.fzoli.mvc.common.request.map.RequestMap;

/**
 *
 * @author zoli
 */
public class PlayerModel extends AbstractOnlineModel<PlayerEvent, PlayerData> implements PlayerKeys {

    public PlayerModel(Connection<Object, Object> connection) {
        super(connection, ModelKeys.PLAYER, PlayerEvent.class, PlayerData.class);
    }

    public void loadCountries(String value, ModelActionListener<PlayerData> callback) {
        getProperties(new RequestMap().setFirst(KEY_REQUEST, REQ_GET_COUNTRIES).setFirst(KEY_VALUE, value), callback);
    }
    
    public void loadRegions(String value, ModelActionListener<PlayerData> callback) {
        getProperties(new RequestMap().setFirst(KEY_REQUEST, REQ_GET_REGIONS).setFirst(KEY_VALUE, value), callback);
    }
    
    public void loadCities(String value, ModelActionListener<PlayerData> callback) {
        getProperties(new RequestMap().setFirst(KEY_REQUEST, REQ_GET_CITIES).setFirst(KEY_VALUE, value), callback);
    }
    
    public PlayerData loadPlayer(String playerName) {
        return getProperties(new RequestMap()
            .setFirst(KEY_USER, playerName));
    }
    
    public int signIn(String user, String password, boolean hash) {
        return askModel(createSignInRequest(user, password, hash));
    }
    
    public void signIn(String user, String password, boolean hash, ModelActionListener<Integer> callback) {
        askModel(createSignInRequest(user, password, hash), callback);
    }
    
    public int signOut() {
        return askModel(createSignOutRequest());
    }
    
    public void signOut(ModelActionListener<Integer> callback) {
        askModel(createSignOutRequest(), callback);
    }
    
    public boolean isEmailFree(String email) {
        return askModel(new RequestMap()
            .setFirst(KEY_REQUEST, REQ_IS_EMAIL_FREE)
            .setFirst(KEY_VALUE, email)) == 1 ? true : false;
    }
    
    public void revalidateEmail(String password, boolean safe, ModelActionListener<Integer> callback) {
        askModel(new RequestMap()
            .setFirst(KEY_REQUEST, safe ? REQ_SAFE_REVALIDATE_EMAIL : REQ_REVALIDATE_EMAIL)
            .setFirst(KEY_PASSWORD, password), callback);
    }
    
    public void suspendAccount(String password, boolean safe, ModelActionListener<Integer> callback) {
        askModel(new RequestMap()
            .setFirst(KEY_REQUEST, safe ? REQ_SAFE_SUSPEND_ACCOUNT : REQ_SUSPEND_ACCOUNT)
            .setFirst(KEY_PASSWORD, password), callback);
    }
    
    public void setActivePermission(int mask, ModelActionListener<Integer> callback) {
        setProperty(new RequestMap().setFirst(KEY_REQUEST, REQ_SET_ACTIVE_PERMISSION).setFirst(KEY_VALUE, Integer.toString(mask)), callback);
    }
    
    public void setEmail(String password, String email, boolean safe, ModelActionListener<Integer> callback) {
        setProperty(new RequestMap()
            .setFirst(KEY_REQUEST, safe ? REQ_SAFE_SET_EMAIL : REQ_SET_EMAIL)
            .setFirst(KEY_PASSWORD, password)
            .setFirst(KEY_VALUE, email), callback);
    }
    
    public void setPassword(String oldPassword, String newPassword, boolean safe, ModelActionListener<Integer> callback) {
        setProperty(new RequestMap()
            .setFirst(KEY_REQUEST, safe ? REQ_SET_SAFE_PASSWORD : REQ_SET_PASSWORD)
            .setFirst(KEY_PASSWORD, oldPassword)
            .setFirst(KEY_VALUE, newPassword), callback);
    }
    
    public void setOnlineStatus(OnlineStatus state, ModelActionListener<Integer> callback) {
        setProperty(new RequestMap()
            .setFirst(KEY_REQUEST, REQ_SET_ONLINE_STATUS)
            .setFirst(KEY_VALUE, state.name()), callback);
    }
    
    public int setPersonalData(PersonalDataType request, String value) {
        return setProperty(createPersonalDataRequest(request, value));
    }
    
    public void setPersonalData(PersonalDataType request, String value, ModelActionListener<Integer> callback) {
        setProperty(createPersonalDataRequest(request, value), callback);
    }
    
    private List<BasePlayer> findPlayerList(PlayerData.PlayerList type) {
        switch (type) {
            case BLOCKED_PLAYERS:
                return getCache().getPlayer().getBlockedUserList();
            case WISHED_FRIENDS:
                return getCache().getPlayer().getFriendWishList();
            case POSSIBLE_FRIENDS:
                return getCache().getPlayer().getPossibleFriends();
            default:
                return getCache().getPlayer().getFriendList();
        }
    }
    
    @Override
    protected void updateCache(List<PlayerEvent> list, PlayerData po) {
        try {
            super.updateCache(list, po);
            if (po.getPlayerName() != null) {
                BasePlayer p;
                List<BasePlayer> l = po.getPlayer().createMergedPlayerList();
                for (PlayerEvent e : list) {
                    switch (e.getType()) {
                        case SIGNIN:
                            p = findPlayer(l, e.getChangedPlayer());
                            if (p != null) {
                                p.setOnline(true);
                            }
                            else {
                                PlayerData data = loadPlayer(e.getChangedPlayer());
                                List<BasePlayer> ls = findPlayerList(data.getAskedPlayerList());
                                ls.add(data.getAskedPlayer());
                                BasePlayer.orderList(ls);
                            }
                            break;
                        case SIGNOUT:
                            p = findPlayer(l, e.getChangedPlayer());
                            if (p != null) p.setOnline(false);
                            break;
                        case VALIDATE:
                            po.getPlayer().setValidated(true);
                            break;
                        case INVALIDATE:
                            po.getPlayer().setEmail("");
                            po.getPlayer().setValidated(false);
                            break;
                        case SUSPEND:
                            p = findPlayer(l, e.getChangedPlayer());
                            if (p != null) {
                                po.getPlayer().getFriendList().remove(p);
                                po.getPlayer().getFriendWishList().remove(p);
                                po.getPlayer().getBlockedUserList().remove(p);
                                po.getPlayer().getPossibleFriends().remove(p);
                            }
                            break;
                        case AVATAR_ENABLE:
                            po.getPlayer().setAvatarEnabled(true);
                            break;
                        case AVATAR_DISABLE:
                            po.getPlayer().setAvatarEnabled(false);
                            break;
                        case RELOAD:
                            PlayerData data = loadPlayer(e.getChangedPlayer());
                            p = findPlayer(l, e.getChangedPlayer());
                            if (p != null) p.reload(data.getAskedPlayer());
                            break;
                        case PERSONAL_DATA_CHANGE:
                            data = loadPlayer(e.getChangedPlayer());
                            p = findPlayer(l, e.getChangedPlayer());
                            if (p != null) p.setPersonalData(data.getAskedPlayer().getPersonalData());
                            break;
                    }
                }
            }
        }
        catch (Exception ex) {
            ;
        }
    }
    
    public BasePlayer findPlayer(String playerName) {
        return findPlayer(getCache().getPlayer().createMergedPlayerList(), playerName);
    }
    
    private BasePlayer findPlayer(List<BasePlayer> l, String name) {
        for (BasePlayer p : l) {
            if (p.getPlayerName().equals(name)) return p;
        }
        return null;
    }
    
    private RequestMap createPersonalDataRequest(PersonalDataType request, String value) {
        return new RequestMap()
            .setFirst(KEY_REQUEST, request.name())
            .setFirst(KEY_VALUE, value);
    }
    
    private RequestMap createSignInRequest(String user, String password, boolean hash) {
        return new RequestMap()
        .setFirst(KEY_REQUEST, hash ? REQ_SAFE_SIGN_IN : REQ_SIGN_IN)
        .setFirst(KEY_USER, user)
        .setFirst(KEY_PASSWORD, <PASSWORD>);
    }

    private RequestMap createSignOutRequest() {
        return new RequestMap()
        .setFirst(KEY_REQUEST, REQ_SIGN_OUT);
    }
    
}<file_sep>package org.dyndns.fzoli.mill.common.key;

/**
 *
 * @author zoli
 */
public enum PersonalDataType {
    FIRST_NAME,
    LAST_NAME,
    INVERSE_NAME,
    BIRTH_DATE,
    SEX,
    COUNTRY,
    REGION,
    CITY,
    CLEAR
}<file_sep>package org.dyndns.fzoli.dao;

import java.sql.Connection;
import java.sql.DriverManager;
import java.util.HashMap;
import java.util.Map;

/**
 *
 * @author zoli
 */
public abstract class AbstractJdbcDAO {
    
    private static final Map<String, Connection> CONNECTIONS = new HashMap<String, Connection>();

//    public AbstractJdbcDAO() {
//        if(getConnection() == null) throw new NullPointerException("JDBC connection is null");
//    }
    
    protected abstract String getUrl();
    
    protected abstract String getDriver();
    
    protected abstract String getUser();
    
    protected abstract String getPassword();
    
    protected Connection getConnection() {
        Connection conn = CONNECTIONS.get(getUrl());
        if (conn == null) {
            conn = createConnection();
            CONNECTIONS.put(getUrl(), conn);
        }
        return conn;
    }
    
    private Connection createConnection() {
        try {
            Class.forName(getDriver()).newInstance();
            return DriverManager.getConnection(getUrl(), getUser(), getPassword());
        }
        catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    
}<file_sep>package org.dyndns.fzoli.mill.client.model;

import java.util.List;
import org.dyndns.fzoli.mill.common.key.ModelKeys;
import org.dyndns.fzoli.mill.common.key.PlayerBuilderKeys;
import org.dyndns.fzoli.mill.common.model.pojo.PlayerBuilderData;
import org.dyndns.fzoli.mill.common.model.pojo.PlayerBuilderEvent;
import org.dyndns.fzoli.mvc.client.connection.Connection;
import org.dyndns.fzoli.mvc.client.event.ModelActionListener;
import org.dyndns.fzoli.mvc.common.request.map.RequestMap;

/**
 *
 * @author zoli
 */
public class PlayerBuilderModel extends AbstractMillModel<PlayerBuilderEvent, PlayerBuilderData> implements PlayerBuilderKeys {

    public PlayerBuilderModel(Connection<Object, Object> connection) {
        super(connection, ModelKeys.PLAYER_BUILDER, PlayerBuilderEvent.class, PlayerBuilderData.class);
    }
    
    public void setUser(String user, ModelActionListener<Integer> callback) {
        setProperty(new RequestMap()
            .setFirst(KEY_REQUEST, REQ_SET_USER)
            .setFirst(KEY_VALUE, user), callback);
    }
    
    public void setEmail(String email, ModelActionListener<Integer> callback) {
        setProperty(new RequestMap()
            .setFirst(KEY_REQUEST, REQ_SET_EMAIL)
            .setFirst(KEY_VALUE, email), callback);
    }
    
    public void createUser(String password, boolean hash, ModelActionListener<Integer> callback) {
        askModel(new RequestMap()
            .setFirst(KEY_REQUEST, hash ? REQ_SAFE_CREATE : REQ_CREATE)
            .setFirst(KEY_VALUE, password), callback);
    }
    
    public void validate() {
        askModel(createValidateMap());
    }
    
    public void validate(ModelActionListener<Integer> callback) {
        askModel(createValidateMap(), callback);
    }
    
    @Override
    protected void updateCache(List<PlayerBuilderEvent> list, PlayerBuilderData cache) {
        try {
            for (PlayerBuilderEvent e : list) {
                if (e.isReset()) cache.reinit();
                cache.setUserCount(e.getUserCount());
            }
        }
        catch (Exception ex) {
            ;
        }
    }
    
    private RequestMap createValidateMap() {
        return new RequestMap()
        .setFirst(KEY_REQUEST, REQ_VALIDATE);
    }
    
}<file_sep>package org.dyndns.fzoli.mill.common.model.pojo;

/**
 *
 * @author zoli
 */
abstract class PlayerBuilderPojo {
    
    private long userCount;

    PlayerBuilderPojo(long userCount) {
        this.userCount = userCount;
    }

    public long getUserCount() {
        return userCount;
    }
    
    protected void setUserCount(long userCount) {
        this.userCount = userCount;
    }
    
}<file_sep>package org.dyndns.fzoli.mill.common.model.pojo;

/**
 *
 * @author zoli
 */
public interface BaseCaptchaPojo {
    
    int getCaptchaWidth();
    
    boolean isCaptchaValidated();
    
    void setCaptchaValidated(boolean validated);
    
    void setCaptchaWidth(int captchaWidth);
    
}<file_sep>package org.dyndns.fzoli.mill.server.model;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.dyndns.fzoli.mill.common.DateUtil;
import org.dyndns.fzoli.mill.common.Permission;
import org.dyndns.fzoli.mill.common.key.ChatKeys;
import org.dyndns.fzoli.mill.common.model.entity.MessageType;
import org.dyndns.fzoli.mill.common.model.pojo.ChatData;
import org.dyndns.fzoli.mill.common.model.pojo.ChatEvent;
import org.dyndns.fzoli.mill.server.model.dao.PlayerDAO;
import org.dyndns.fzoli.mill.server.model.entity.ConvertUtil;
import org.dyndns.fzoli.mill.server.model.entity.Message;
import org.dyndns.fzoli.mill.server.model.entity.Player;
import org.dyndns.fzoli.mvc.common.request.map.RequestMap;

/**
 *
 * @author zoli
 */
public class ChatModel extends AbstractOnlineModel<ChatEvent, ChatData> implements ChatKeys {

    private static final PlayerDAO DAO = new PlayerDAO();
    
    public void sendMessage(Message message) {
        callOnPlayerChanged(ChatModel.class, new ChatEvent(message.getAddress().getPlayerName(), ConvertUtil.createMessage(message)));
    }
    
    @Override
    protected ChatData getProperties(HttpServletRequest hsr, RequestMap rm) {
        Player me = getPlayer();
        String action = rm.getFirst(KEY_REQUEST);
        if (me != null && action != null) {
            if (action.equals(REQ_GET_MESSAGES)) {
                String player = rm.getFirst(KEY_PLAYER);
                if (player != null) {
                    Player p = DAO.getPlayer(player);
                    if (p != null) {
                        try { // return every message after the specified date
                            Date d = new Date(Long.parseLong(rm.getFirst(KEY_DATE)));
                            return new ChatData(me.getPlayerName(), ConvertUtil.createMessageList(me.getMessages(p, d)));
                        }
                        catch (Exception ex) { // return only unreaded messages if date is not specified
                            return new ChatData(me.getPlayerName(), ConvertUtil.createMessageList(me.getUnreadedMessages(p)));
                        }
                    }
                }
            }
        }
        return new ChatData(getPlayerName(), me == null ? null : me.getUnreadedMessagesCount());
    }

    @Override
    protected int askModel(HttpServletRequest hsr, RequestMap rm) {
        Player me = getPlayer();
        String action = rm.getFirst(KEY_REQUEST);
        if (action != null && action.equals(REQ_SYNC)) {
            try {
                long remote = Long.parseLong(rm.getFirst(KEY_DATE));
                long local = DateUtil.getDateInTimeZone(new Date(), "GMT").getTime();
                return (int)(local - remote);
            }
            catch (Exception ex) {
                return 0;
            }
        }
        if (me != null && action != null) {
            String player = rm.getFirst(KEY_PLAYER);
            if (player != null) {
                Player p = DAO.getPlayer(player);
                if (p != null) {
                    if (action.equals(REQ_UPDATE_READ_DATE)) {
                        if (me.updateMessageReadDate(p)) {
                            DAO.save(me);
                            return 1;
                        }
                        else {
                            return 0;
                        }
                    }
                    if (action.equals(REQ_REMOVE_MESSAGES)) {
                        if (DAO.removeMessages(me, p)) {
                            if (me.getFriendList().contains(p) || p.canUsePermission(me, Permission.SEND_PERSON_MESSAGE)) callOnPlayerChanged(ChatModel.class, new ChatEvent(p.getPlayerName(), me.getPlayerName()));
                            return 1;
                        }
                        else {
                            return 0;
                        }
                    }
                    if (p.getFriendList().contains(me) || me.canUsePermission(p, Permission.SEND_PERSON_MESSAGE)) {
                        String value = rm.getFirst(KEY_VALUE);
                        if (value != null) {
                            if (action.equals(REQ_SEND_MESSAGE)) {
                                Message msg = new Message(p, value);
                                DAO.save(msg);
                                me.getPostedMessages().add(msg);
                                DAO.save(me);
                                msg.setSender(me);
                                sendMessage(msg);
                                return 1;
                            }
                        }
                    }
                    else {
                        return me.canUsePermission(p, Permission.DETECT_INVISIBLE_STATUS) ? 0 : 1;
                    }
                }
            }
        }
        return 0;
    }

    @Override
    protected void onPlayerChanged(ChatEvent evt) {
        if (evt.isClear() && evt.getPlayerName().equals(getPlayerName())) {
            addEvent(evt);
        }
        if (!evt.isClear() && evt.getMessage().getAddress().equals(getPlayerName())) {
            addEvent(evt);
        }
    }

    @Override
    protected int setProperty(HttpServletRequest hsr, RequestMap rm) {
        return 0;
    }

}<file_sep>package org.dyndns.fzoli.email;

import java.io.File;
import java.util.Properties;
import javax.mail.*;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

public class GMailSender {

    private static class AuthenticatorData {
        
        String user;
        Authenticator authenticator;

        AuthenticatorData(String user, Authenticator authenticator) {
            this.user = user;
            this.authenticator = authenticator;
        }
        
    }
    
    private static Properties createGmailProperties() {
        Properties props = new Properties();
        props.put("mail.smtp.host", "smtp.gmail.com");
        props.put("mail.smtp.socketFactory.port", "465");
        props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
        props.put("mail.smtp.auth", "true");
        props.put("mail.smtp.port", "465");
        return props;
    }
    
    private static AuthenticatorData createAuthenticator(File fXmlFile) {
        try {
            DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
            Document doc = dBuilder.parse(fXmlFile);
            doc.getDocumentElement().normalize();
            Element e = doc.getDocumentElement();
            final String user = e.getAttribute("user");
            final String password = e.getAttribute("password");
            if (user == null || password == null) throw new Exception("Wrong GMail XML file");
            Authenticator authenticator = new Authenticator() {

                @Override
                protected PasswordAuthentication getPasswordAuthentication() {
                    return new PasswordAuthentication(user, password);
                }
                    
            };
            return new AuthenticatorData(user, authenticator);
        }
        catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }
    
    public static void sendEmail(File xml, String address, String subject, String msg) {
        AuthenticatorData data = createAuthenticator(xml);
        Session session = Session.getDefaultInstance(createGmailProperties(), data.authenticator);
        try {
            Message message = new MimeMessage(session);
            message.setFrom(new InternetAddress(data.user));
            message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(address));
            message.setSubject(subject);
            message.setContent(msg, "text/html;charset=UTF-8");
            Transport.send(message);
        }
        catch (MessagingException e) {
            throw new RuntimeException(e);
        }
    }
    
    public static void main(String[] args) {
        sendEmail(new File("/home/zoli/gmail-authenticator.xml"), "<EMAIL>", "Teszt üzenet", "Kedves e-mail szűrő, kérlek ne töröld az üzenetet.<br />Köszöni a Java.<h1>Öt szép szűz lány őrült írót nyúz.</h1>");
    }
    
}<file_sep>package org.dyndns.fzoli.location.dao;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dyndns.fzoli.dao.AbstractJdbcDAO;
import org.dyndns.fzoli.location.entity.City;
import org.dyndns.fzoli.location.entity.Country;
import org.dyndns.fzoli.location.entity.Region;

/**
 *
 * @author zoli
 */
public abstract class CityDAO extends AbstractJdbcDAO {

    private final Log LOG = LogFactory.getLog(CityDAO.class);
    private static final String ID = "ID", COUNTRY = "COUNTRY", REGION = "REGION", REGION_CODE = "REGION_CODE", NAME = "NAME", ACCENT_NAME = "ACCENT_NAME", POPULATION = "POPULATION", LATITUDE = "LATITUDE", LONGITUDE = "LONGITUDE", CITY = "CITY";

    public Country getCountryByName(String name) {
        if (name == null || name.isEmpty()) return null;
        return getFirst(getCountries(NAME, name, true));
    }
    
    public List<Region> getRegionsByCountryId(String countryId) {
        return getRegions(COUNTRY, countryId, true);
    }
    
    public List<Region> getRegions(String countryName, String regionName) {
        return getRegionsByCountryAndRegionName(countryName, regionName, true);
    }
    
    public List<Region> getRegionsByCountryName(String countryName) {
        Country country = getCountryByName(countryName);
        if (country == null) return new ArrayList<Region>();
        return getRegionsByCountryId(country.getID());
    }
    
    public List<City> getCities(String countryName, String regionName, String cityName) {
        return getCities(countryName, regionName, cityName, true);
    }
    
    public List<Country> findCountriesByName(String name) { // country auto complette
        if (name == null || name.isEmpty()) return new ArrayList<Country>();
        return getCountries(NAME, name, false);
    }
    
    public List<Region> findRegions(String countryName, String regionName) { // region auto complette
        return getRegionsByCountryAndRegionName(countryName, regionName, false);
    }
    
    public List<City> findCities(String countryName, String regionName, String cityName) { // city auto complette
        return getCities(countryName, regionName, cityName, false);
    }
    
    protected abstract String getLocation();
    
    @Override
    protected String getUrl() {
        return "jdbc:h2:" + getLocation();
    }

    @Override
    protected String getDriver() {
        return "org.h2.Driver";
    }

    @Override
    protected String getUser() {
        return "sa";
    }

    @Override
    protected String getPassword() {
        return "";
    }
    
    private List<City> getCities(String countryName, String regionName, String cityName, boolean equals) {
        cityName = StringEscapeUtils.escapeSql(cityName);
        List<Region> regions = getRegions(countryName, regionName);
        if (regions.isEmpty()) return new ArrayList<City>();
        String sql = "SELECT * FROM " + CITY + " WHERE REGION IN(";
        for (int i = 0; i < regions.size(); i++) {
            sql += regions.get(i).getID();
            if (i != regions.size() - 1) sql += ", ";
        }
        sql += ") AND (" + (equals ? (/*NAME + " = '" + cityName + "' OR " + */ACCENT_NAME + " = '" + cityName + "'") : ("LOCATE('" + cityName.toUpperCase() + "', UPPER(" + NAME + ")) = 1 OR LOCATE('" + cityName.toUpperCase() + "', UPPER(" + ACCENT_NAME + ")) = 1")) + ");";
        LOG.info(sql);
        return getObjects(sql, City.class);
    }
    
    private List<Region> getRegionsByCountryAndRegionName(String countryName, String regionName, boolean equals) {
        Country country = getCountryByName(countryName);
        if (country == null) return new ArrayList<Region>();
        return getObjects(new String[]{COUNTRY, NAME}, new boolean[]{true}, new String[]{country.getID(), regionName}, new boolean[]{true, equals}, Region.class, REGION);
    }
    
    private List<Region> getRegions(final String column, final String value, final boolean equals) {
        return getObjects(column, value, equals, Region.class, REGION);
    }
    
    private List<Country> getCountries(final String column, final String value, final boolean equals) {
        return getObjects(column, value, equals, Country.class, COUNTRY);
    }
    
    private <T> List<T> getObjects(final String column, String value, final boolean equals, final Class<T> clazz, final String from) {
        return getObjects(new String[]{column}, new boolean[]{false}, new String[]{value}, new boolean[]{equals}, clazz, from);
    }
    
    private <T> List<T> getObjects(final String[] columns, final boolean[] ands, String[] values, final boolean[] equals, final Class<T> clazz, final String from) {
        String sql = "SELECT * FROM " + from;
        if (values != null) {
            sql += " WHERE ";
            final int lastIndex = columns.length - 1;
            for (int i = 0; i <= lastIndex; i++) {
                String value = StringEscapeUtils.escapeSql(values[i]);
                sql += equals[i] ? columns[i] + " = '" + value + "'" : "LOCATE('" + value.toUpperCase() + "', UPPER(" + columns[i] + ")) = 1";
                if (i != lastIndex) sql += " " + (ands[i] ? "AND" : "OR") + " ";
            }
        }
        sql += ';';
        LOG.info(sql);
        return getObjects(sql, clazz);
    }
    
    private <T> List<T> getObjects(final String sql, final Class<T> clazz) {
        final List<T> l = new ArrayList<T>();
        try {
            final Statement statement = getConnection().createStatement();
            final ResultSet results = statement.executeQuery(sql);
            while (results.next()) {
                Object o = null;
                if (clazz.equals(Country.class)) o = new Country(results.getString(ID), results.getString(NAME));
                if (clazz.equals(Region.class)) o = new Region(results.getLong(ID), results.getString(COUNTRY), results.getString(REGION_CODE), results.getString(NAME));
                if (clazz.equals(City.class)) o = new City(results.getLong(ID), results.getLong(REGION), results.getString(NAME), results.getString(ACCENT_NAME), results.getInt(POPULATION), results.getDouble(LATITUDE), results.getDouble(LONGITUDE));
                if (o != null) l.add((T)o);
            }
            results.close();
            statement.close();
        }
        catch(SQLException ex) {
            ex.printStackTrace();
        }
        return l;
    }
    
    private static <T> T getFirst(final List<T> l) {
        if (l == null || l.isEmpty()) return null;
        return l.get(0);
    }
    
}<file_sep>package org.dyndns.fzoli.mill.server.model.entity;

import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

/**
 *
 * @author zoli
 */
@Entity
public class City implements Serializable {
    
    private static final long serialVersionUID = 1L;
    
    @Id
    @GeneratedValue
    private long id;
    
    private Region region;
    private double latitude, longitude;
    private String name, accentName;

    public City() {
    }

    public City(Region region, String name, String accentName, double latitude, double longitude) {
        this.region = region;
        this.name = name;
        this.accentName = accentName;
        this.latitude = latitude;
        this.longitude = longitude;
    }

    public long getId() {
        return id;
    }

    public Region getRegion() {
        return region;
    }
    
    public String getName() {
        return name;
    }

    public String getAccentName() {
        return accentName;
    }
    
    public double getLatitude() {
        return latitude;
    }

    public double getLongitude() {
        return longitude;
    }

    @Override
    public String toString() {
        return getName() + '#' + getId();
    }
    
}
<file_sep>package org.dyndns.fzoli.mill.server.model;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.util.ArrayList;
import javax.servlet.http.HttpServletRequest;
import nl.captcha.Captcha;
import nl.captcha.backgrounds.BackgroundProducer;
import nl.captcha.backgrounds.FlatColorBackgroundProducer;
import nl.captcha.gimpy.GimpyRenderer;
import nl.captcha.gimpy.RippleGimpyRenderer;
import nl.captcha.noise.CurvedLineNoiseProducer;
import nl.captcha.noise.NoiseProducer;
import nl.captcha.text.producer.DefaultTextProducer;
import nl.captcha.text.producer.TextProducer;
import nl.captcha.text.renderer.ColoredEdgesWordRenderer;
import nl.captcha.text.renderer.WordRenderer;
import org.dyndns.fzoli.mill.common.key.BaseKeys;
import org.dyndns.fzoli.mill.common.key.ModelKeys;
import org.dyndns.fzoli.mill.server.model.entity.ConvertUtil;
import org.dyndns.fzoli.mill.server.model.entity.Player;
import org.dyndns.fzoli.mvc.common.request.map.RequestMap;
import org.dyndns.fzoli.mvc.server.model.JSONModel;

/**
 *
 * @author zoli
 */
abstract class AbstractMillModel<EventObj, PropsObj> extends JSONModel<EventObj, PropsObj> implements BaseKeys {
    
    private final static GimpyRenderer GR = new RippleGimpyRenderer();
    private final static TextProducer TP = new DefaultTextProducer(6, new char[]{'q', 'w', 'e', 'r', 't', 'z', 'u', 'o', 'p', 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'y', 'x', 'c', 'v', 'b', 'n', 'm'});
    
    private int w = 200, h = 50;
    private Color fgc = Color.WHITE, bgc = Color.BLACK;
    private BackgroundProducer bp = new FlatColorBackgroundProducer(bgc);
    private NoiseProducer np = createProducer();
    private WordRenderer wr = createRenderer();
    
    private boolean captchaValidated = false;
    private Captcha captcha;

    @Override
    protected RenderedImage getImage(HttpServletRequest hsr, RequestMap rm) {
        String action = rm.getFirst(KEY_REQUEST);
        if (action != null) {
            if (action.equals(REQ_GET_CAPTCHA)) return getCaptcha();
            if (action.equals(REQ_CREATE_CAPTCHA)) return createCaptcha();
        }
        return super.getImage(hsr, rm);
    }

    @Override
    protected int askModel(HttpServletRequest hsr, RequestMap rm) {
        String action = rm.getFirst(KEY_REQUEST);
        if (action != null) {
            String val = rm.getFirst(KEY_VALUE);
            if (val != null) {
                if (action.equals(REQ_VALIDATE_CAPTCHA)) {
                    return validate(val) ? 0 : 1;
                }
                try {
                    if (action.equals(REQ_SET_CAPTCHA_COLOR)) {
                        setCaptchaBgColor(Boolean.parseBoolean(val));
                        return 0;
                    }
                    if (action.equals(REQ_SET_CAPTCHA_SIZE)) {
                        setCaptchaSize(Integer.parseInt(val));
                        return 0;
                    }
                }
                catch (Exception ex) {}
            }
        }
        return -1;
    }
    
    public PlayerModel getPlayerModel() {
        return getPlayerModel(false);
    }
    
    public PlayerModel getPlayerModel(boolean init) {
        return getModelMap().get(ModelKeys.PLAYER, init, PlayerModel.class);
    }

    public int getCaptchaWidth() {
        return w;
    }
    
    public Player getPlayer() {
        PlayerModel model = getPlayerModel();
        if (model == null) return null;
        return model.getPlayer();
    }
    
    public void reinitPlayer() {
        PlayerModel model = getPlayerModel();
        if (model == null) return;
        model.reinitPlayer();
    }
    
    public org.dyndns.fzoli.mill.common.model.entity.Player getCommonPlayer() {
        PlayerModel model = getPlayerModel();
        if (model == null) return null;
        return model.getCommonPlayer();
    }
    
    public String getPlayerName() {
        Player player = getPlayer();
        if (player == null) return null;
        return player.getPlayerName();
    }

    protected boolean isCaptchaValidated() {
        return captchaValidated;
    }
    
    protected boolean validate(String answer) {
        boolean ok = captcha == null ? false : captcha.isCorrect(answer.toLowerCase());
        if (!ok) createCaptcha();
        captchaValidated = ok;
        return ok;
    }
    
    protected void removeCaptcha() {
        captcha = null;
        captchaValidated = false;
    }

    protected RenderedImage getCaptcha() {
        if (captcha == null) return createCaptcha();
        BufferedImage img = captcha.getImage();
        Graphics2D g = (Graphics2D) img.getGraphics();
        g.setColor(fgc);
        g.drawRect(0, 0, img.getWidth() - 1, img.getHeight() - 1);
        return img;
    }
    
    protected void setCaptchaBgColor(boolean white) {
        bgc = white ? Color.WHITE : Color.BLACK;
        fgc = white ? Color.BLACK : Color.WHITE;
        bp = new FlatColorBackgroundProducer(bgc);
        np = createProducer();
        wr = createRenderer();
    }
    
    protected void setCaptchaSize(int w) {
        if (w < 200) w = 200;
        if (w > 1000) w = 1000;
        this.w = w;
        this.h = w / 4;
        wr = createRenderer();
        np = createProducer();
        removeCaptcha();
    }
    
    protected NoiseProducer createProducer() {
        return new CurvedLineNoiseProducer(bgc, w / 100);
    }
    
    protected WordRenderer createRenderer() {
        return new ColoredEdgesWordRenderer(new ArrayList<Color>() {{add(fgc);}}, new ArrayList<Font>() {{add(new Font("Arial", Font.BOLD, h - (w / 20)));}}, w / 100);
    }
    
    protected RenderedImage createCaptcha() {
        removeCaptcha();
        captcha = new Captcha.Builder(w, h).addText(TP, wr).addBackground(bp).gimp(GR).addBorder().addNoise(np).build();        
        return getCaptcha();
    }

}<file_sep>package org.dyndns.fzoli.mill.common.key;

/**
 *
 * @author zoli
 */
public enum PlayerAvatarReturn {
    OK,
    NOT_OK
}<file_sep>package org.dyndns.fzoli.mill.server.test.objectdb;

import java.util.List;
import javax.persistence.EntityManager;
import static org.dyndns.fzoli.mill.server.test.objectdb.Util.*;
import org.junit.AfterClass;
import static org.junit.Assert.*;
import org.junit.BeforeClass;
import org.junit.Test;

/**
 * ObjectDB second test.
 * 1. Create database connection.
 * 2. Check whether FirstTest finished.
 * 3. Clear messages.
 * 4. Try read players.
 * 5. Create two messages.
 * 6. Reading back and testing.
 * @author zoli
 */
public class SecondTest {
    
    private static EntityManager db;
    
    public SecondTest() {
    }

    @BeforeClass
    public static void setUpClass() throws Exception {
        System.out.println("Create database connection.");
        db = createEntityManager();
        assertEquals("There should be exactly 2 players in the database. Please run FirstTest!", 2, getCount(db, Player.class));
    }
    
    @AfterClass
    public static void tearDownClass() throws Exception {
        System.out.println("Close database connection.");
        db.close();
    }
    
    @Test(timeout=10000)
    public void testTwo() {
        
        System.out.println("Clear messages.");
        remove(db, Message.class);
        
        System.out.println("Try read players...");
        Player p1 = getPlayer(db, PLAYER1);
        assertNotNull(p1);
        Player p2 = getPlayer(db, PLAYER2);
        assertNotNull(p2);
        
        System.out.println("Create message 1.");
        Message m1 = new Message(p2, "message 1");
        assertTrue(save(db, m1));
        p1.getPostedMessages().add(m1);
        assertTrue(save(db, p1));
        
        System.out.println("Create message 2.");
        Message m2 = new Message(p1, "message 2");
        assertTrue(save(db, m2));
        p2.getPostedMessages().add(m2);
        assertTrue(save(db, p2));
        
        System.out.println("Testing...");
        List<Message> messages = getMessages(db, PLAYER1);
        assertNotNull(messages);
        assertEquals(1, messages.size());
        assertEquals(1, p1.getReceivedMessages().size());
        assertEquals(OnlineStatus.ONLINE, p1.getOnlineStatus());
        
    }
     
}<file_sep>package org.dyndns.fzoli.mill.common;

import java.util.ArrayList;
import java.util.List;

/** Kuka...
 *  Egy adott jog gyakorlásának alap feltétele, hogy a maszk adjon rá jogot.
 *  A három kiegészítő alapelv:
 *  a) Egy adott jog akkor gyakorolható egy másik felhasználón, ha
 *     a másik felhasználó maszkja kisebb (egyenlőség nem megengedett).
 *     (Ebből adódik, hogy fontos a jogok felsorolásának sorrendje.)
 *  b) A felhasználó nem tud olyan jogot adni/elvenni, amivel ő nem rendelkezik
 *     még akkor sem, ha van joga a maszk szerkesztésére.
 *  c) Kitüntetett szerepű maszk a -1.
 *     - Ezen maszkkal az összes jog gyakorolható.
 *     - A legnagyobb maszknak számít.
 *     - A programban soha nem adható senkinek és nem vehető el senkitől.
 */
    
/** Alap jogkezelési gondolat.
 * 
 * Minden jog aktiválható/inaktiválható, ha birtokolja valaki, kivéve a védelem jogát, ami mindig aktív.
 * 
 *  Jogok:
 * 
 *  - statisztika elrejtése: nem láthatják mások, hogy mennyit nyert és veszített a felhasználó a dámában
 * 
 *  - láthatatlan státusz észlelése: annak ellenére, hogy a felhasználó láthatatlan,
 *    ezzel a joggal lehet látni, ha online
 * 
 *  - láthatatlanság nyilvántartásban: a nyilvántartásból elrejtheti magát a felhasználó.
 *    az elrejtés nem érvényes a barátlistára.
 *  
 *  - inaktív felhasználók listázása, adataiknak olvasása
 * 
 *  - chatelés barátlistán kívüliekkel illetve azokkal, akiknél tiltva van a joggal rendelkező -> olyan rendszerüzenet küldése, ami csak 1 embernél, a címzettnél jelenik meg
 * 
 *  - rendszerüzenet küldés: mindenki számára azonnal felugró üzenet küldése
 * 
 *  - felhasználó bannolása: képesség felhasználó bannolására
 *    (bannolás: felhasználó bejelentkezésének tiltása és ha online, azonnali kijelentkeztetése)
 * 
 *  - felhasználó törlése (felfüggesztése): képesség felhasználó törlésére
 *    (törlés: szem. adatok nullázása, felhasználó státusz inaktívvá állítás ami elrejti a nyilvántartásból őt)
 * 
 *  - jogok szerkesztése: képesség jog adására és elvételére
 * 
 *  - védelem: a jogot birtokló felhasználón nem tud senki jogot gyakorolni.
 *             Ez a jog a többivel ellentétben nem aktiválható/inaktiválható.
 *             pl. nem törölheti; láthatatlanságát nem tudja detektálni,
 *                 ha rejtve van a nyilvántartásban és van joga észleléshez, akkor sem látja;
 *                 nem szerkesztheti jogait; nem tudja elrejteni előtte a statisztikáját;
 *                 nem tudja detektálni ha elrejtette magát
 */ 
/**   Kitüntetett szerepű maszk a -1.
 *    - Ezen maszkkal az összes jog aktív, tehát nem szerkeszthetők a jogai a felhasználónak.
 *    - A védelem joga nem korlátozza és csak ő adhatja illetve veheti el ezt a jogot.
 *    - A programon belül soha nem adható senkinek és nem vehető el senkitől.
 *    - A programon belül nem tudja saját magát kitörölni ellentétben a többiekkel.
 */

/**
 *
 * @author zoli
 */
public enum Permission {
    //TODO: ötlet - a 'send person message' jog legyen 'state_inverse' és legyen külön kategória a barátlistánál, ahova az ebben a módban lévő online felhasználók megjelennek ÉS bármelyiküknek lehessen üzenetet küldeni ha nincs a jogot birtokló tiltólistáján
    HIDDEN_STATISTICS(Group.STATE_INVERSE),
    DETECT_INVISIBLE_STATUS(Group.STATE_NORMAL),
    DETECT_SUSPENDED_PLAYER(Group.STATE_NORMAL),
//    HIDDEN_PLAYER_DETECT(Group.STATE_NORMAL),
//    SHOW_ALL_PERSONAL_DATA(Group.STATE_NORMAL),
    HIDDEN_MODE(Group.STATE_INVERSE),
//    SEE_EVERYONES_AVATAR(Group.STATE_NORMAL),
//    CHAT_EVERYONE(Group.STATE_NORMAL),
    SEND_PERSON_MESSAGE(Group.SYSTEM),
    SEND_SYSTEM_MESSAGE(Group.SYSTEM),
    PLAYER_BANN(Group.TARGET),
    PLAYER_SUSPEND(Group.TARGET),
    PERMISSION_EDIT(Group.TARGET),
    SHIELD_MODE(Group.STATE_INVERSE);
    
    public static final int ROOT = -1;
    private static final int MIN = 0, MAX = (int) Math.pow(2, Permission.values().length) - 1;
    
    private static final List<Integer> MASKS = new ArrayList<Integer>() {
        {
            for (int i = MIN; i <= MAX; i++) {
                add(i);
            }
        }
    };
    
    public static enum Group {
        TARGET, STATE_NORMAL, STATE_INVERSE, SYSTEM
    };
    
    private final Group GROUP;
    
    private Permission(Group group) {
        GROUP = group;
    }
    
    public Group getGroup() {
        return GROUP;
    }
    
    public int getMask() {
        return getMask(this);
    }
    
    public boolean hasPermission(int mask) {
        return hasPermission(mask, this);
    }
    
    public boolean hasAllPermission() {
        return hasAllPermission(getMask());
    }
    
    public int incPermission(int mask) {
        return incPermission(mask, this);
    }
    
    public int decPermission(int mask) {
        return decPermission(mask, this);
    }
    
    public static List<Integer> getMasks() {
        return MASKS;
    }
    
    public static List<Integer> getMasks(Permission p) {
        List<Integer> l = new ArrayList<Integer>();
        for (int mask : getMasks()) {
            if (hasPermission(mask, p)) l.add(mask);
        }
        return l;
    }
    
    public static int getMask(Permission p) {
        return (int) Math.pow(2, p.ordinal());
    }
    
    public static int getMask(List<Permission> permissions) {
        int i = 0;
        List<Permission> tmp = new ArrayList<Permission>();
        for (Permission p : permissions) {
            if (tmp.contains(p)) continue;
            tmp.add(p);
            i += p.getMask();
        }
        return i;
    }
    
    public static List<Permission> getPermissions(int mask) {
        List<Permission> ps = new ArrayList<Permission>();
        for (Permission p : Permission.values()) {
            if (p.hasPermission(mask)) ps.add(p);
        }
        return ps;
    }
    
    public static boolean hasPermission(int mask, Permission p) {
        if (mask == ROOT) return true;
        if (mask < MIN || mask > MAX) return false;
        return (mask & getMask(p)) != 0;
    }
    
    public static boolean hasAllPermission(int mask) {
        if (mask == ROOT) return true;
        return mask == MAX;
    }
    
    public static int incPermission(int mask, Permission p) {
        if (!hasPermission(mask, p)) return mask + getMask(p);
        else return mask;
    }
    
    public static int decPermission(int mask, Permission p) {
        if (hasPermission(mask, p)) return mask - getMask(p);
        else return mask;
    }
    
}