/*
 * Aipo is a groupware program developed by Aimluck,Inc.
 * Copyright (C) 2004-2008 Aimluck,Inc.
 * http://aipostyle.com/
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package com.aimluck.eip.services.security;

import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;

import javax.servlet.ServletConfig;

import org.apache.cayenne.access.DataContext;
import org.apache.cayenne.exp.Expression;
import org.apache.cayenne.exp.ExpressionFactory;
import org.apache.cayenne.query.SelectQuery;
import org.apache.jetspeed.om.security.Permission;
import org.apache.jetspeed.om.security.Role;
import org.apache.jetspeed.services.JetspeedSecurity;
import org.apache.jetspeed.services.logging.JetspeedLogFactoryService;
import org.apache.jetspeed.services.logging.JetspeedLogger;
import org.apache.jetspeed.services.rundata.JetspeedRunData;
import org.apache.jetspeed.services.rundata.JetspeedRunDataService;
import org.apache.jetspeed.services.security.JetspeedSecurityCache;
import org.apache.jetspeed.services.security.JetspeedSecurityException;
import org.apache.jetspeed.services.security.JetspeedSecurityService;
import org.apache.jetspeed.services.security.PermissionException;
import org.apache.jetspeed.services.security.PermissionManagement;
import org.apache.turbine.services.InitializationException;
import org.apache.turbine.services.TurbineBaseService;
import org.apache.turbine.services.TurbineServices;
import org.apache.turbine.services.resources.ResourceService;
import org.apache.turbine.services.rundata.RunDataService;

import com.aimluck.eip.cayenne.om.security.TurbinePermission;
import com.aimluck.eip.cayenne.om.security.TurbineRolePermission;
import com.aimluck.eip.orm.DatabaseOrmService;

/**
 * パーミッションを管理するクラスです。 <br />
 * 
 */
public class ALPermissionManagement extends TurbineBaseService implements
    PermissionManagement {

  private static final JetspeedLogger logger = JetspeedLogFactoryService
      .getLogger(ALPermissionManagement.class.getName());

  private JetspeedRunDataService runDataService = null;

  private final static String CASCADE_DELETE = "programmatic.cascade.delete";

  private final static boolean DEFAULT_CASCADE_DELETE = true;

  private final static String CONFIG_SYSTEM_PERMISSIONS = "system.permissions";

  private boolean cascadeDelete;

  private final static String CACHING_ENABLE = "caching.enable";

  private boolean cachingEnable = true;

  private Vector systemPermissions = null;

  /**
   * 
   */
  public Iterator getPermissions(String rolename)
      throws JetspeedSecurityException {
    Role role = null;
    try {
      if (cachingEnable) {
        Iterator iterator = JetspeedSecurityCache.getPermissions(rolename);
        if (iterator != null) {
          return iterator;
        }
      }
      role = JetspeedSecurity.getRole(rolename);
    } catch (JetspeedSecurityException e) {
      logger.error("Failed to Retrieve Role: ", e);
      throw new PermissionException("Failed to Retrieve Role: ", e);
    }

    List rels;
    HashMap perms;

    try {
      Expression exp = ExpressionFactory.matchDbExp(
          TurbineRolePermission.ROLE_ID_PK_COLUMN, Integer
              .valueOf(role.getId()));

      SelectQuery query = new SelectQuery(TurbineRolePermission.class, exp);

      DataContext dataContext = DatabaseOrmService.getInstance()
          .getDataContext();
      rels = dataContext.performQuery(query);

      if (rels.size() > 0) {
        perms = new HashMap(rels.size());
      } else
        perms = new HashMap();

      for (int ix = 0; ix < rels.size(); ix++) {
        TurbineRolePermission rel = (TurbineRolePermission) rels.get(ix);
        Permission perm = rel.getTurbinePermission();
        perms.put(perm.getName(), perm);
      }
    } catch (Exception e) {
      logger.error("Failed to retrieve permissions ", e);
      throw new PermissionException("Failed to retrieve permissions ", e);
    }
    return perms.values().iterator();
  }

  /**
   * 
   */
  public Iterator getPermissions() throws JetspeedSecurityException {
    List permissions;
    try {
      SelectQuery query = new SelectQuery(TurbinePermission.class);

      DataContext dataContext = DatabaseOrmService.getInstance()
          .getDataContext();
      permissions = dataContext.performQuery(query);

    } catch (Exception e) {
      logger.error("Failed to retrieve permissions ", e);
      throw new PermissionException("Failed to retrieve permissions ", e);
    }
    return permissions.iterator();
  }

  /**
   * 
   */
  public void addPermission(Permission permission)
      throws JetspeedSecurityException {
    if (permissionExists(permission.getName())) {
      throw new PermissionException("The permission '" + permission.getName()
          + "' already exists");
    }

    try {
      DataContext dataContext = DatabaseOrmService.getInstance()
          .getDataContext();
      // 新規オブジェクトモデル
      TurbinePermission tpermission = (TurbinePermission) dataContext
          .createAndRegisterNewObject(TurbinePermission.class);
      tpermission.setName(permission.getName());
      tpermission.setOBJECTDATA(null);
      dataContext.commitChanges();

    } catch (Exception e) {
      String message = "Failed to create permission '" + permission.getName()
          + "'";
      logger.error(message, e);
      throw new PermissionException(message, e);
    }
  }

  /**
   * 
   */
  public void savePermission(Permission permission)
      throws JetspeedSecurityException {

    if (!permissionExists(permission.getName())) {
      throw new PermissionException("The permission '" + permission.getName()
          + "' doesn't exists");
    }

    try {
      DataContext dataContext = DatabaseOrmService.getInstance()
          .getDataContext();

      if (permission instanceof TurbinePermission) {
        dataContext.commitChanges();
      } else {
        throw new PermissionException(
            "TurbinePermissionManagment: Permission is not a Turbine permission, cannot update");
      }

    } catch (Exception e) {
      String message = "Failed to create permission '" + permission.getName()
          + "'";
      logger.error(message, e);
      throw new PermissionException(message, e);
    }

  }

  /**
   * 
   */
  public void removePermission(String permissionName)
      throws JetspeedSecurityException {
    try {

      if (systemPermissions.contains(permissionName)) {
        throw new PermissionException("[" + permissionName
            + "] is a system permission and cannot be removed");
      }
      Permission permission = this.getPermission(permissionName);
      DataContext dataContext = DatabaseOrmService.getInstance()
          .getDataContext();

      if (cascadeDelete) {
        Expression exp = ExpressionFactory.matchDbExp(
            TurbineRolePermission.PERMISSION_ID_PK_COLUMN, Integer
                .valueOf(permission.getId()));
        SelectQuery query = new SelectQuery(TurbineRolePermission.class, exp);
        List rolepermissions = dataContext.performQuery(query);
        dataContext.deleteObjects(rolepermissions);
      }
      dataContext.deleteObject((TurbineRolePermission) permission);
      dataContext.commitChanges();

      if (cachingEnable) {
        JetspeedSecurityCache.removeAllPermissions(permissionName);
      }
    } catch (Exception e) {

      String message = "Failed to remove permission '" + permissionName + "'";
      logger.error(message, e);
      throw new PermissionException(message, e);
    } finally {

    }

  }

  /**
   * 
   */
  public void grantPermission(String roleName, String permissionName)
      throws JetspeedSecurityException {
    try {
      Role role = JetspeedSecurity.getRole(roleName);
      Permission permission = this.getPermission(permissionName);

      DataContext dataContext = DatabaseOrmService.getInstance()
          .getDataContext();
      // 新規オブジェクトモデル
      TurbineRolePermission role_permission = (TurbineRolePermission) dataContext
          .createAndRegisterNewObject(TurbineRolePermission.class);
      role_permission.setRoleId(Integer.parseInt(role.getId()));
      role_permission.setPermissionId(Integer.parseInt(permission.getId()));
      dataContext.commitChanges();

    } catch (Exception e) {
      String message = "Grant permission '" + permissionName + "' to role '"
          + roleName + "' failed: ";
      logger.error(message, e);
      throw new PermissionException(message, e);
    }
  }

  /**
   * 
   */
  public void revokePermission(String roleName, String permissionName)
      throws JetspeedSecurityException {
    try {
      Role role = JetspeedSecurity.getRole(roleName);
      Permission permission = this.getPermission(permissionName);

      DataContext dataContext = DatabaseOrmService.getInstance()
          .getDataContext();
      Expression exp1 = ExpressionFactory.matchDbExp(
          TurbineRolePermission.ROLE_ID_PK_COLUMN, Integer
              .valueOf(role.getId()));
      Expression exp2 = ExpressionFactory.matchDbExp(
          TurbineRolePermission.PERMISSION_ID_PK_COLUMN, Integer
              .valueOf(permission.getId()));
      SelectQuery query = new SelectQuery(TurbineRolePermission.class);
      query.setQualifier(exp1);
      query.andQualifier(exp2);
      List list = dataContext.performQuery(query);

      dataContext.deleteObjects(list);

      if (cachingEnable) {
        JetspeedSecurityCache.removePermission(roleName, permissionName);
      }
    } catch (Exception e) {
      String message = "Revoke permission '" + permissionName + "' to role '"
          + roleName + "' failed: ";
      logger.error(message, e);
      throw new PermissionException(message, e);
    }

  }

  /**
   * 
   */
  public boolean hasPermission(String roleName, String permissionName)
      throws JetspeedSecurityException {
    List permissions;

    try {
      if (cachingEnable) {
        return JetspeedSecurityCache.hasPermission(roleName, permissionName);
      }

      Role role = JetspeedSecurity.getRole(roleName);
      Permission permission = this.getPermission(permissionName);

      DataContext dataContext = DatabaseOrmService.getInstance()
          .getDataContext();
      Expression exp1 = ExpressionFactory.matchDbExp(
          TurbineRolePermission.ROLE_ID_PK_COLUMN, Integer
              .valueOf(role.getId()));
      Expression exp2 = ExpressionFactory.matchDbExp(
          TurbineRolePermission.PERMISSION_ID_PK_COLUMN, Integer
              .valueOf(permission.getId()));
      SelectQuery query = new SelectQuery(TurbineRolePermission.class);
      query.setQualifier(exp1);
      query.andQualifier(exp2);
      permissions = dataContext.performQuery(query);

    } catch (Exception e) {
      String message = "Failed to check permission '" + permissionName + "'";
      logger.error(message, e);
      throw new PermissionException(message, e);
    }
    return (permissions.size() > 0);
  }

  /**
   * 
   */
  public Permission getPermission(String permissionName)
      throws JetspeedSecurityException {
    List permissions;

    try {
      DataContext dataContext = DatabaseOrmService.getInstance()
          .getDataContext();

      SelectQuery query = new SelectQuery(TurbinePermission.class);
      permissions = dataContext.performQuery(query);

    } catch (Exception e) {
      String message = "Failed to retrieve permission '" + permissionName + "'";
      logger.error(message, e);
      throw new PermissionException(message, e);
    }
    if (permissions.size() > 1) {
      throw new PermissionException(
          "Multiple Permissions with same permissionname '" + permissionName
              + "'");
    }
    if (permissions.size() == 1) {
      TurbinePermission permission = (TurbinePermission) permissions.get(0);
      return permission;
    }
    throw new PermissionException("Unknown permission '" + permissionName + "'");

  }

  protected JetspeedRunData getRunData() {
    JetspeedRunData rundata = null;
    if (this.runDataService != null) {
      rundata = this.runDataService.getCurrentRunData();
    }
    return rundata;
  }

  /**
   * 
   * @param permissionName
   * @return
   * @throws PermissionException
   */
  protected boolean permissionExists(String permissionName)
      throws PermissionException {
    List permissions;
    try {
      DataContext dataContext = DatabaseOrmService.getInstance()
          .getDataContext();
      Expression exp = ExpressionFactory.matchExp(
          TurbinePermission.PERMISSION_NAME_PROPERTY, permissionName);
      SelectQuery query = new SelectQuery(TurbinePermission.class, exp);
      permissions = dataContext.performQuery(query);
    } catch (Exception e) {
      logger.error("Failed to check account's presence", e);
      throw new PermissionException("Failed to check account's presence", e);
    }
    if (permissions.size() < 1) {
      return false;
    }
    return true;
  }

  /**
   * 
   */
  public synchronized void init(ServletConfig conf)
      throws InitializationException {
    if (getInit())
      return;

    super.init(conf);

    ResourceService serviceConf = ((TurbineServices) TurbineServices
        .getInstance()).getResources(JetspeedSecurityService.SERVICE_NAME);

    this.runDataService = (JetspeedRunDataService) TurbineServices
        .getInstance().getService(RunDataService.SERVICE_NAME);

    cascadeDelete = serviceConf.getBoolean(CASCADE_DELETE,
        DEFAULT_CASCADE_DELETE);
    cachingEnable = serviceConf.getBoolean(CACHING_ENABLE, cachingEnable);
    systemPermissions = serviceConf.getVector(CONFIG_SYSTEM_PERMISSIONS,
        new Vector());
    setInit(true);
  }

}
