package org.itscool.weber.user;

import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.itscool.commons.bean.BeanFactory;

import org.itscool.commons.bean.BeanUtil;
import org.itscool.commons.logging.AbstractLog;
import org.itscool.commons.logging.SimpleLog;
import org.itscool.commons.util.AngouUtil;
import org.itscool.commons.util.StringUtil;

/**
 * ユーザ認証を管理するクラスです<br>
 * このクラスはorg.itscool.weber.user.RoleFileManagerクラスのデータベース版です<br>
 *
 * <p>
 * RoleDbManagerクラスは、ユーザとパスワードを定義したテーブルを
 * 用意することで、アプリケーション内でユーザ認証を行うことができます<br>
 * </p>
 *
 * <p>
 * Weberフレームワークでは、org.itscool.weber.aciton.ActionクラスのisUserCheck()
 * メソッド内で利用しています。<br/>
 * FrontServletからActionが呼ばれた場合、Actionを実行する前にActionを要求した
 * ユーザがActionを実行する権限があるか判断しています<br/>
 * </p>
 *
 * <p>
 * Weberフレームワークでデータベースによるユーザ認証機能を有効にするためには、
 * di-config.xmlファイルに以下の書式でRoleDbManagerを登録する必要があります<br/>
 * </p>
 *
 * <pre>
 * &lt;bean id="WeberUserManager" type="org.itscool.weber.user.RoleDbManager" singleton="true"/&gt;
 * </pre>
 * <br/>
 *
 * <p>
 * 又、このクラスはコネクションの取得にorg.itscool.commons.TransactionUtilクラスを
 * 使用しています。<br/>
 * TransactionUtilを個別に作成することも可能ですが、org.itscool.commons.ConnectionManager
 * クラスを利用すれば、この手間を省くことができます。<br/>
 * </p>
 *
 * <p>
 * ConnectionManagerの登録もdi-config.xmlで行います。<br/>
 * JDBCとDataSourceのどちらを利用するかで設定内容が変わります。
 * </p>
 *
 * <p>以下は、JDBCを利用する場合の設定例です</p>
 * <pre>
 * &lt;bean id="ConnectionManager" type="org.itscool.commons.connection.OriginalConnectionManager" singleton="true"&gt;
 *   &lt;property name="url" value="jdbc:hsqldb:hsql://localhost"/&gt;
 *   &lt;property name="user" value="sa"/&gt;
 *   &lt;property name="pwd" value=""/&gt;
 *   &lt;property name="drvname" value="org.hsqldb.jdbcDriver"/&gt;
 *   &lt;property name="timeout" value="300"/&gt;
 *   &lt;property name="initSize" value="3"/&gt;
 *   &lt;property name="maxSize" value="10"/&gt;
 *   &lt;property name="mode" value="false"/&gt;
 *   &lt;property name="showSql" value="false"/&gt;
 * &lt;/bean&gt;
 * </pre><br/>
 *
 * <p>以下は、DataSourceを利用する場合の設定例です</p>
 * <pre>
 * &lt;bean id="ConnectionManager" type="org.itscool.commons.connection.DataSourceManager" singleton="true"&gt;
 *   &lt;property name="dsName" value="java:comp/env/weber-jndi"/&gt;
 *   &lt;property name="mode" value="false"/&gt;
 *   &lt;property name="showSql" value="true"/&gt;
 * &lt;/bean&gt;
 * </pre>
 *
 * <p>ConnectionManagerインターフェースはitscool-commons.jarに含まれています</p>
 *
 * <p>
 * ユーザ情報をデータベースで管理するためには、あらかじめ以下のテーブルを準備して
 * おく必要があります<br>
 * </p>
 * <p>
 * (1) 権限テーブル（WEBER_ROLE)
 * <table border="1">
 * <tr>
 * 		<td>フィールド名</td>
 * 		<td>型</td>
 * 		<td>初期値</td>
 * 		<td>必須</td>
 * 		<td>サイズ</td>
 * 		<td>主キー</td>
 * 		<td>参照テーブル</td>
 * 		<td>参照キー</td>
 * </tr>
 * <tr>
 * 		<td>ID</td>
 * 		<td>Integer</td>
 * 		<td>　</td>
 * 		<td>true</td>
 * 		<td>　</td>
 * 		<td>true</td>
 * 		<td>　</td>
 * 		<td>　</td>
 * </tr>
 * <tr>
 * 		<td>NAME</td>
 * 		<td>VARCHAR(CHAR)</td>
 * 		<td>　</td>
 * 		<td>true</td>
 * 		<td>任意のサイズ(255バイトを推奨)</td>
 * 		<td>　</td>
 * 		<td>　</td>
 * 		<td>　</td>
 * </tr>
 * </table>
 * </p><br/>
 *
 * <p>
 * (2) ユーザ情報テーブル（WEBER_USER)
 * <table border="1">
 * <tr>
 * 		<td>フィールド名</td>
 * 		<td>型</td>
 * 		<td>初期値</td>
 * 		<td>必須</td>
 * 		<td>サイズ</td>
 * 		<td>主キー</td>
 * 		<td>参照テーブル</td>
 * 		<td>参照キー</td>
 * </tr>
 * <tr>
 * 		<td>ID</td>
 * 		<td>Integer</td>
 * 		<td>　</td>
 * 		<td>true</td>
 * 		<td>　</td>
 * 		<td>true</td>
 * 		<td>　</td>
 * 		<td>　</td>
 * </tr>
 * <tr>
 * 		<td>NAME</td>
 * 		<td>VARCHAR(CHAR)</td>
 * 		<td>　</td>
 * 		<td>true</td>
 * 		<td>任意のサイズ(255バイトを推奨)</td>
 * 		<td>　</td>
 * 		<td>　</td>
 * 		<td>　</td>
 * </tr>
 * <tr>
 * 		<td>PASSWORD</td>
 * 		<td>VARCHAR(CHAR)</td>
 * 		<td>　</td>
 * 		<td>true</td>
 * 		<td>任意のサイズ(255バイトを推奨)</td>
 * 		<td>　</td>
 * 		<td>　</td>
 * 		<td>　</td>
 * </tr>
 * <tr>
 * 		<td>ROLE_ID</td>
 * 		<td>Integer</td>
 * 		<td>　</td>
 * 		<td>true</td>
 * 		<td>　</td>
 * 		<td></td>
 * 		<td>WEBER_ROLE</td>
 * 		<td>ID</td>
 * </tr>
 * </table>
 * </p><br/>
 *
 * <p>
 * 権限テーブルにはWebアプリケーションで利用可能なロールを定義します<br/>
 * ユーザ情報テーブルにはWebアプリケーションで利用可能なユーザ名とパスワード
 * を定義します。<br>
 * </p>
 * <p>
 * org.itscool.weber.user.CreateUserForDbクラスを利用すれば、パスワードを暗号化した
 * ユーザ情報を作成することができます<br>
 * 詳しくは<a href="./CreateUserForDb.html">org.itscool.weber.user.CreateUserForDbクラスの説明</a>を参照してください<br>
 * </p>
 * @author KANO
 * @since jdk1.4.1
 * @version 1.00A 2005/9/8
 */
public class UserEntityManager implements UserManager{
    private AbstractLog log;
    private boolean initFlg = false;
    
    /** 唯一のWeberUserManagerインスタンス */
    private static UserEntityManager instance = null;
    
    /**
     * WeberUserManagerインスタンスを取得します
     * @return RoleMangerインスタンスを返却します
     */
    public static UserEntityManager getInstance(){
        if( instance == null ){
            instance = new UserEntityManager();
        }
        return instance;
    }
    
    /** プライベートなコンストラクタ */
    private UserEntityManager(){
        BeanFactory factory = BeanFactory.getInstance();
        try{
            log = (AbstractLog)factory.getInstance("Log");
            if(!log.isInit()){
                log = (AbstractLog)factory.createInstance("Log");
            }
            initFlg = true;
        }catch(Exception ioe){
            log = SimpleLog.getInstance();
            log.info(ioe.getMessage());
        }
    }
    
    public boolean isInit(){
        return initFlg;
    }
    
    public void init(){
//        initFlg = true;
    }
    
    /**
     * 指定されたユーザがアクセスを許可されているか判定します<BR>
     * 暗号化されたパスワードはAngouクラスによって複合化されます
     * @param roles 権限情報
     * @param user ユーザ名
     * @return このユーザによるアクセスが許可されている場合はtrueを返します
     */
    public boolean isUserCheck(HashMap roles, User user ) {
        if( user == null ){
            return false;
        }
        if( roles == null || roles.size() == 0 ){
            return isRoles(user);
        }else{
            return isRoles(roles, user);
        }
    }
    
    /**
     * ユーザ認証を行います
     * @param roles 権限情報ファイルのリスト
     * @param user ユーザ情報
     * @return このユーザが妥当であればtrueを返します
     */
    private boolean isRoles(HashMap roles, User user ) {
        boolean ret = false;
        Set keySet = roles.keySet();
        Iterator it = keySet.iterator();
        
        UserDao dao = new UserDao();
        while( it.hasNext() ){
            String key = (String)it.next();
            String roleName = (String)roles.get(key);
            String userName = user.getName();
            User dbUser = dao.getUser(roleName, userName);
            ret = isRole(user, dbUser);
            if( ret ){
                //権限情報をセット
                user.setRole(dbUser.getRole());
                return ret;
            }
        }
        return ret;
    }
    
    /**
     * ユーザ認証を行います<BR>
     * RoleFileManagerに登録されている全ての権限情報から該当する
     * ユーザが存在するか調べます。
     * @param user ユーザ情報
     * @return このユーザが妥当であればtrueを返します
     */
    private boolean isRoles(User user ){
        boolean ret = false;
        UserDao dao = new UserDao();
        List users = dao.getUsers(user.getName());
        
        for(int i=0; i<users.size(); i++){
            User dbUser = (User)users.get(i);
            ret = isRole(user, dbUser);
            if( ret ){
                //権限情報をセット
                Role userRole = dbUser.getRole();
                user.setRole(userRole);
                return ret;
            }
        }
        return ret;
    }
    
    /**
     * 指定されたユーザがアクセスを許可されているか判定します<BR>
     * 暗号化されたパスワードはAngouクラスによって複合化されます
     * @param User ユーザ情報
     * @param roleFile ロードされた権限情報ファイル
     * @return このユーザによるアクセスが許可されている場合はtrueを返します
     */
    private boolean isRole(User loginUser, User dbUser){
        
        if( dbUser == null ) return false;
        if( loginUser == null ) return false;
        if( !dbUser.getName().equals(loginUser.getName())) return false;
        
        //パスワードの複合化
        String pwd = "";
        try{
            byte[] pwdBytes = StringUtil.decodeHex(dbUser.getPwd(), "ISO-8859-1");
            if(pwdBytes!=null){
                pwd = AngouUtil.decrypt(dbUser.getName(), pwdBytes );
            }else{
                log.warn("passward decode hex is missing. user["+dbUser.getName()+"] is non decode hex.");
                pwd = dbUser.getPwd();
            }
        }catch(IOException ioe){
            throw new RuntimeException(ioe.getMessage());
        }
        if( pwd == null ){
            if(loginUser.getPwd() == null || loginUser.getPwd().length()==0){
                return true;
            }else{
                return false;
            }
        }
        if( !pwd.equals(loginUser.getPwd())) return false;
        
        //ユーザIDの引継ぎ
        loginUser.setId(dbUser.getId());
        
        return true;
        
    }
    /**
     * パスワードファイルの再読み込みを行います。
     */
    public void reload() {
        //non process
    }
    
    public List getAllData(){
        UserDao userDao = new UserDao();
        List users = userDao.getAllData();
        for(int i=0; i<users.size(); i++){
            User user = (User)users.get(i);
            //パスワードの複合化
            if( user != null ){
                String pwd = "";
                try{
                    byte[] pwdBytes = StringUtil.decodeHex(user.getPwd(), "ISO-8859-1");
                    pwd = AngouUtil.decrypt(user.getName(), pwdBytes );
                }catch(IOException ioe){
                    throw new RuntimeException(ioe.getMessage());
                }
                user.setPwd(pwd);
            }
        }
        return users;
    }
    
    /**
     * 指定されたユーザ名に基づくユーザ情報を取得します
     * @param roleName ロール名
     * @param userName ユーザ名
     * @return ユーザ情報を返します
     */
    public User getUser(String roleName, String userName){
        RoleDao dao = new RoleDao();
        Role role = dao.getRole(roleName);
        if( role != null ){
            UserDao userDao = new UserDao();
            User user = userDao.getUser(role, userName);
            //パスワードの複合化
            if( user != null ){
                try{
                    String pwd = decrypt(user.getName(), user.getPwd());
                    user.setPwd(pwd);
                }catch(IOException ioe){
                    log.warn("passward decode hex is missing. user["+user.getName()+"] is non decode hex.");
                }
            }
            return user;
        }
        return null;
    }
    
    /**
     * 暗号化されたユーザパスワードを復元します
     * @param userName ユーザ名
     * @param srcPwd 暗号化されたパスワード
     * @return 複合化されたパスワード
     */
    public static String decrypt(String userName, String srcPwd)
    throws IOException{
        byte[] pwdBytes = StringUtil.decodeHex(srcPwd, "ISO-8859-1");
        String dstPwd = AngouUtil.decrypt(userName, pwdBytes );
        return dstPwd;
    }
    
    /**
     * ユーザパスワードを暗号化します
     * @param userName ユーザ名
     * @param srcPwd ユーザパスワード
     * @return 暗号化されたパスワード
     */
    public static String encrypt(String userName, String srcPwd)
    throws IOException{
        //パスワードを暗号化
        byte[] pwdAngou = AngouUtil.encrypt(userName, srcPwd);
        String pwdAngouStr = StringUtil.encodeHex(pwdAngou, "ISO-8859-1");
        return pwdAngouStr;
    }
    /**
     * 指定された権限を持つユーザ情報のリストを取得します
     * @param roleName ロール名
     * @return ユーザ情報のリスト
     */
    public HashMap getUsers(String roleName){
        HashMap users = new HashMap();
        RoleDao roleDao = new RoleDao();
        Role role = roleDao.getRole(roleName);
        UserDao dao = new UserDao();
        List lst = dao.getUsers(role);
        for(int i=0; i<lst.size(); i++){
            User user = (User)lst.get(i);
            try{
                String pwd = decrypt(user.getName(), user.getPwd());
                user.setPwd(pwd);
            }catch(IOException ioe){
                log.warn("passward decode hex is missing. user["+user.getName()+"] is non decode hex.");
            }
            users.put(user.getName(), user);
        }
        return users;
    }
    /**
     * 指定されたユーザ情報を削除します
     * @param user ユーザ情報
     * @deprecated ユーザアプリケーションからこのメソッドは使用しないで下さい
     * @throws IOException
     */
    public void delete(User user) throws IOException{
        UserDao dao = new UserDao();
        dao.delete(user);
    }
    
    /**
     * 指定されたユーザ情報を削除します
     * @param roleName ロール名
     * @param user ユーザ情報
     * @deprecated ユーザアプリケーションからこのメソッドは使用しないで下さい
     * @throws IOException
     */
    public void delete(String roleName, User user) throws IOException{
        UserDao dao = new UserDao();
        if(user.getRoleId() == -1){
            RoleDao roleDao = new RoleDao();
            Role role = roleDao.getRole(roleName);
            user.setRoleId(role.getId());
        }
        dao.delete(user);
    }
    
    /**
     * 指定されたユーザ情報を削除します
     * @param roleName ロール名
     * @param name ユーザ名
     * @deprecated ユーザアプリケーションからこのメソッドは使用しないで下さい
     * @throws IOException
     */
    public void delete(String roleName, String name) throws IOException{
        User user = new User();
        user.setName(name);
        delete(roleName, user);
    }
    /**
     * 指定されたユーザ情報を追加します<BR>
     * ユーザ情報の追加に成功した場合はUser.idにID値がセットされます。
     * @param user ユーザ情報
     * @return 追加に成功した場合はtrueを返します。
     * 既に登録済みの場合はfalseを返します
     * @deprecated ユーザアプリケーションからこのメソッドは使用しないで下さい
     * @throws IOException
     */
    public boolean add(User user) throws IOException{
        UserDao dao = new UserDao();
        User newUser = (User)BeanUtil.copyInstance(user, User.class.getName());

        //パスワードを暗号化
        String pwdAngouStr = encrypt(user.getName(), user.getPwd());
        newUser.setPwd(pwdAngouStr);

        int id = dao.save(newUser);
        //IDをセット
        user.setId(id);
        
        return true;
    }
    
    /**
     * 指定されたユーザ情報を追加します<BR>
     * ユーザ情報の追加に成功した場合はUser.idにID値がセットされます。
     * @param roleName ロール名
     * @param user ユーザ情報
     * @return 追加に成功した場合はtrueを返します。
     * 既に登録済みの場合はfalseを返します
     * @deprecated ユーザアプリケーションからこのメソッドは使用しないで下さい
     * @throws IOException
     */
    public boolean add(String roleName, User user) throws IOException{
        if(user.getRoleId() == -1){
            RoleDao dao = new RoleDao();
            Role role = dao.getRole(roleName);
            if( role == null ){
                return false;
            }
            user.setRoleId(role.getId());
        }
        UserDao dao = new UserDao();
        if( getUser(roleName, user.getName()) == null ){
            User newUser = (User)BeanUtil.copyInstance(user, User.class.getName());
            
            //パスワードを暗号化
            String pwdAngouStr = encrypt(user.getName(), user.getPwd());
            newUser.setPwd(pwdAngouStr);
            
            int id = dao.save(newUser);
            //IDをセット
            user.setId(id);
            return true;
        }else{
            return false;
        }
    }
    
    /**
     * 指定されたユーザ情報を追加します
     * @param roleName ロール名
     * @param name ユーザ名
     * @param pwd パスワード
     * @return 追加に成功した場合はtrueを返します。
     * 既に登録済みの場合はfalseを返します
     * @deprecated ユーザアプリケーションからこのメソッドは使用しないで下さい
     * @throws IOException
     */
    public boolean add(String roleName, String name, String pwd) throws IOException{
        User user = new User();
        user.setName(name);
        user.setPwd(pwd);
        return add(roleName, user);
    }
    /**
     * 指定されたユーザ情報を更新します
     * @param user ユーザ情報
     * @deprecated ユーザアプリケーションからこのメソッドは使用しないで下さい
     * @throws IOException
     */
    public void update(User user) throws IOException{
        UserDao dao = new UserDao();
        User newUser = (User)BeanUtil.copyInstance(user, User.class.getName());
        String pwdAngouStr = encrypt(user.getName(), user.getPwd());
        newUser.setPwd(pwdAngouStr);
        
        dao.update(newUser);
    }
    
    /**
     * 指定されたユーザ情報を更新します
     * @param roleName ロール名
     * @param user ユーザ情報
     * @deprecated ユーザアプリケーションからこのメソッドは使用しないで下さい
     * @throws IOException
     */
    public void update(String roleName, User user) throws IOException{
        RoleDao roleDao = new RoleDao();
        Role role = roleDao.getRole(roleName);
        user.setId(role.getId());
        
        UserDao dao = new UserDao();
        User newUser = (User)BeanUtil.copyInstance(user, User.class.getName());
        String pwdAngouStr = encrypt(user.getName(), user.getPwd());
        newUser.setPwd(pwdAngouStr);
        
        dao.update(newUser);
        
    }
    
    public boolean addRole(Role role){
        RoleDao roleDao = new RoleDao();
        
        if( roleDao.getRole(role.getName()) != null ){
            return false;
        }
        
        roleDao.save(role);
        
        return true;
    }
    
    public boolean deleteRole(Role role){
        RoleDao roleDao = new RoleDao();
        UserDao userDao = new UserDao();
        
        if( userDao.getUsers(role).size() > 0 ){
            return false;
        }
        
        roleDao.delete(role);
        
        return true;
    }
    
    
}
