/*
 * This software is distributed under following license based on modified BSD
 * style license.
 * ----------------------------------------------------------------------
 * 
 * Copyright 2009 The Nimbus2 Project. All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer. 
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE NIMBUS PROJECT ``AS IS'' AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
 * NO EVENT SHALL THE NIMBUS PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * 
 * The views and conclusions contained in the software and documentation are
 * those of the authors and should not be interpreted as representing official
 * policies, either expressed or implied, of the Nimbus2 Project.
 */
package jp.ossc.nimbus.service.validator;

import java.io.*;
import java.util.*;

import org.apache.commons.jexl2.Expression;
import org.apache.commons.jexl2.JexlEngine;
import org.apache.commons.jexl2.JexlContext;
import org.apache.commons.jexl2.MapContext;

import jp.ossc.nimbus.beans.*;
import jp.ossc.nimbus.core.*;
import jp.ossc.nimbus.util.ClassMappingTree;
import jp.ossc.nimbus.util.validator.*;
import jp.ossc.nimbus.service.beanflow.*;

/**
 * BeanFlowサービスを使ったバリデータ。<p>
 * 
 * @author M.Takata
 */
public class BeanFlowValidatorService extends ServiceBase
 implements Validator, BeanFlowValidatorServiceMBean{
    
    private static final long serialVersionUID = -5396783198786410663L;
    
    private Map<String, String> classMapping;
    private ClassMappingTree<String> classMap;
    private String[] conditions;
    private List<Condition> conditionList;
    private String defaultBeanFlowKey;
    private ServiceName beanFlowFactoryServiceName;
    private BeanFlowFactory beanFlowFactory;
    
    public void setClassMapping(Map<String, String> mapping){
        classMapping = mapping;
    }
    public Map<String, String> getClassMapping(){
        return classMapping;
    }
    
    public void setConditions(String[] conditions){
        this.conditions = conditions;
    }
    public String[] getConditions(){
        return conditions;
    }
    
    public void setDefaultBeanFlowKey(String beanFlowKey){
        defaultBeanFlowKey = beanFlowKey;
    }
    public String getDefaultBeanFlowKey(){
        return defaultBeanFlowKey;
    }
    
    public void setBeanFlowFactoryServiceName(ServiceName name){
        beanFlowFactoryServiceName = name;
    }
    public ServiceName getBeanFlowFactoryServiceName(){
        return beanFlowFactoryServiceName;
    }
    
    public void setBeanFlowFactory(BeanFlowFactory factory){
        beanFlowFactory = factory;
    }
    public BeanFlowFactory getBeanFlowFactory(){
        return beanFlowFactory;
    }
    
    public void startService() throws Exception{
        if(beanFlowFactoryServiceName != null){
            beanFlowFactory = (BeanFlowFactory)ServiceManagerFactory
                .getServiceObject(beanFlowFactoryServiceName);
        }
        if(beanFlowFactory == null){
            throw new IllegalArgumentException("BeanFlowFactory is null.");
        }
        final Set<String> flowKeySet = beanFlowFactory.getBeanFlowKeySet();
        
        if(classMapping != null && classMapping.size() != 0){
            classMap = new ClassMappingTree<String>(null);
            for(Map.Entry<String, String> entry : classMapping.entrySet()){
                final Class<?> clazz = Utility.convertStringToClass((String)entry.getKey());
                final String beanFlowKey = (String)entry.getValue();
                if(!flowKeySet.contains(beanFlowKey)){
                    throw new IllegalArgumentException("BeanFlow is not found : " + beanFlowKey);
                }
                classMap.add(clazz, beanFlowKey);
            }
        }else{
            if(classMap != null){
                classMap.clear();
            }
        }
        
        if(conditions != null && conditions.length != 0){
            if(conditionList != null){
                conditionList.clear();
            }else{
                conditionList = new ArrayList<Condition>();
            }
            for(int i = 0; i < conditions.length; i++){
                final String condition = conditions[i];
                final int index = condition.lastIndexOf('=');
                if(index == 0 || index == -1
                     || index == condition.length() - 1){
                    throw new IllegalArgumentException("Condition is illegal : " + condition);
                }
                final String cond = condition.substring(0, index);
                final String beanFlowKey = condition.substring(index + 1);
                if(!flowKeySet.contains(beanFlowKey)){
                    throw new IllegalArgumentException("BeanFlow is not found : " + beanFlowKey);
                }
                conditionList.add(new Condition(cond, beanFlowKey));
            }
        }else{
            if(conditionList != null){
                conditionList.clear();
            }
        }
        if(defaultBeanFlowKey != null
             && !flowKeySet.contains(defaultBeanFlowKey)){
            throw new IllegalArgumentException("BeanFlow is not found : " + defaultBeanFlowKey);
        }
        if((classMapping == null || classMapping.size() == 0)
             && (conditionList == null || conditionList.size() == 0)
             && defaultBeanFlowKey == null){
            throw new IllegalArgumentException("BeanFlowKey is not specified.");
        }
    }
    
    /**
     * 指定されたオブジェクトを検証する。<p>
     *
     * @param obj 検証対象のオブジェクト
     * @return 検証結果。検証成功の場合true
     * @exception ValidateException 検証に失敗した場合
     */
    public boolean validate(Object obj) throws ValidateException{
        String beanFlowKey = null;
        if(obj != null && classMap != null){
            beanFlowKey = (String)classMap.getValue(obj.getClass());
        }
        if(beanFlowKey == null
             && conditionList != null && conditionList.size() != 0){
            for(int i = 0, imax = conditionList.size(); i < imax; i++){
                final Condition condition = (Condition)conditionList.get(i);
                if(condition.evaluate(obj)){
                    beanFlowKey = condition.beanFlowKey;
                    break;
                }
            }
        }
        if(beanFlowKey == null){
            beanFlowKey = defaultBeanFlowKey;
        }
        if(beanFlowKey == null){
            throw new ValidateException("Validate BeanFlow is not found.");
        }
        try{
            final Object ret = beanFlowFactory.createFlow(beanFlowKey).execute(obj);
            if(ret == null || !(ret instanceof Boolean)){
                throw new ValidateException("Result of BeanFlow is not boolean.");
            }
            return ((Boolean)ret).booleanValue();
        }catch(Exception e){
            throw new ValidateException(e);
        }
    }
    
    private static class Condition implements Serializable{
        
        private static final long serialVersionUID = -9155271482408748880L;
        
        public String beanFlowKey;
        
        private transient Expression expression;
        private String condition;
        
        private static final String PROP_FUNCTION_NAME = "prop";
        private static final String VALUE = "value";
        
        Condition(String cond, String beanFlowKey) throws Exception{
            initCondition(cond);
            condition = cond;
            this.beanFlowKey = beanFlowKey;
        }
        
        private void initCondition(String cond) throws Exception{
            JexlEngine jexl = new JexlEngine();
            jexl.setSilent(true);
            Map<String, Object> funcs = new HashMap<String, Object>();
            PropertyAccess propAccess = new PropertyAccess();
            propAccess.setIgnoreNullProperty(true);
            funcs.put(PROP_FUNCTION_NAME, propAccess);
            jexl.setFunctions(funcs);
            expression = jexl.createExpression(cond);
            evaluate("", true);
        }
        
        public boolean evaluate(Object object) throws ValidateException{
            return evaluate(object, false);
        }
        
        protected boolean evaluate(Object object, boolean isTest) throws ValidateException{
            JexlContext jexlContext = new MapContext();
            jexlContext.set(VALUE, object);
            
            Object exp = expression.evaluate(jexlContext);
            if(exp instanceof Boolean){
                return ((Boolean)exp).booleanValue();
            }else{
                if(exp == null && isTest){
                    return true;
                }
                throw new ValidateException(expression.getExpression());
            }
        }
        
        private void readObject(ObjectInputStream in)
         throws IOException, ClassNotFoundException{
            in.defaultReadObject();
            try{
                initCondition(condition);
            }catch(Exception e){
                // 起こらないはず
            }
        }
    }
}
