org.opentestsystem.delivery.testreg.eligibility.EligibilityEvaluatorCache.java Source code

Java tutorial

Introduction

Here is the source code for org.opentestsystem.delivery.testreg.eligibility.EligibilityEvaluatorCache.java

Source

/*
Educational Online Test Delivery System Copyright (c) 2013 American Institutes for Research
    
Distributed under the AIR Open Source License, Version 1.0 See accompanying file AIR-License-1_0.txt or at
http://www.smarterapp.org/documents/American_Institutes_for_Research_Open_Source_Software_License.pdf
 */

package org.opentestsystem.delivery.testreg.eligibility;

import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import org.apache.commons.beanutils.PropertyUtils;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.opentestsystem.delivery.testreg.domain.ARTHelpers;
import org.opentestsystem.delivery.testreg.domain.Assessment;
import org.opentestsystem.delivery.testreg.domain.CacheMap;
import org.opentestsystem.delivery.testreg.domain.ExplicitEligibility;
import org.opentestsystem.delivery.testreg.domain.HierarchyLevel;
import org.opentestsystem.delivery.testreg.domain.ImplicitEligibilityRule;
import org.opentestsystem.delivery.testreg.domain.InstitutionEntity;
import org.opentestsystem.delivery.testreg.domain.Sb11Entity;
import org.opentestsystem.delivery.testreg.domain.Sb11SuperEntity;
import org.opentestsystem.delivery.testreg.domain.Student;
import org.opentestsystem.delivery.testreg.domain.event.AssessmentModificationEvent;
import org.opentestsystem.delivery.testreg.domain.event.BatchStudentModificationEvent;
import org.opentestsystem.delivery.testreg.domain.event.ExplicitEligibilityModificationEvent;
import org.opentestsystem.delivery.testreg.domain.event.StudentModificationEvent;
import org.opentestsystem.delivery.testreg.domain.exception.EligibilityException;
import org.opentestsystem.delivery.testreg.persistence.AssessmentRepository;
import org.opentestsystem.delivery.testreg.persistence.ExplicitEligibilityRepository;
import org.opentestsystem.delivery.testreg.persistence.StudentRepository;
import org.opentestsystem.delivery.testreg.persistence.criteria.dependencyresolvers.EligibilityDependencyResolver;
import org.opentestsystem.delivery.testreg.service.CacheMapService;
import org.opentestsystem.delivery.testreg.service.EligibilityService;
import org.opentestsystem.delivery.testreg.service.Sb11EntityRepositoryService;
import org.opentestsystem.shared.progman.client.ProgManClient;
import org.opentestsystem.shared.progman.client.domain.Tenant;
import org.opentestsystem.shared.progman.client.domain.TenantType;
import org.opentestsystem.shared.security.domain.SbacEntity;
import org.opentestsystem.shared.security.service.TenancyService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

@Component
class EligibilityEvaluatorCache {

    private final ProgManClient progmanClient;
    private final TenancyService tenancyService;
    private final AssessmentRepository assessmentRepository;
    private final Sb11EntityRepositoryService sb11EntityService;

    // Unused but for CGLIB to create proxy objects (for @Cacheable).
    EligibilityEvaluatorCache() {
        progmanClient = null;
        tenancyService = null;
        assessmentRepository = null;
        sb11EntityService = null;
    }

    @Autowired
    EligibilityEvaluatorCache(ProgManClient progmanClient, TenancyService tenancyService,
            AssessmentRepository assessmentRepository, Sb11EntityRepositoryService sb11EntityService) {
        this.progmanClient = progmanClient;
        this.tenancyService = tenancyService;
        this.assessmentRepository = assessmentRepository;
        this.sb11EntityService = sb11EntityService;
    }

    @Cacheable("tenantById")
    public Tenant getTenantById(String tenantId) {
        return this.progmanClient.getTenantById(tenantId);
    }

    @Cacheable("applicableTenants")
    public List<String> getApplicableTenantIds(final Set<SbacEntity> possibleTenants) {
        return Lists.transform(tenancyService.getApplicableTenants(possibleTenants),
                ARTHelpers.TENANT_ID_TRANSFORMER);
    }

    @Cacheable("implicitAssessments")
    public List<Assessment> getImplicitAssessments(String institutionIdentifier, String stateAbbreviation) {
        // create the initial set with the institution level
        final Set<SbacEntity> possibleTenants = Sets
                .newHashSet(new SbacEntity(TenantType.INSTITUTION, institutionIdentifier, "nothing"));
        // go up the hierarchy to find all the entities up to the client and add to the hash set
        // we need to know if any of the levels are tenants so we grab all possible assessments for the hierarchy
        Sb11Entity entity = sb11EntityService.findByEntityIdAndStateAbbreviation(institutionIdentifier,
                stateAbbreviation, InstitutionEntity.class);
        while (entity != null) {
            if (entity.getParentEntityType() != null) {
                final TenantType tenantType = convertHierarchyLevelToTenantType(entity.getParentEntityType());
                if (tenantType != null) {
                    possibleTenants.add(new SbacEntity(tenantType, entity.getParentEntityId(), "nothing"));
                }
            }
            entity = entity.getParentEntityId() != null ? sb11EntityService.getParentEntity(entity) : null;
        }
        // find all assessments for possible tenants
        List<Assessment> implicitAssessments = assessmentRepository.findByEligibilityTypeAndTenantIdIsIn("IMPLICIT",
                getApplicableTenantIds(possibleTenants));
        return CollectionUtils.isEmpty(implicitAssessments) ? new ArrayList<Assessment>() : implicitAssessments; // repo can return null
    }

    private static TenantType convertHierarchyLevelToTenantType(final HierarchyLevel level) {
        TenantType converted = null;

        switch (level) {
        case DISTRICT:
            converted = TenantType.DISTRICT;
            break;
        case GROUPOFDISTRICTS:
            converted = TenantType.DISTRICT_GROUP;
            break;
        case GROUPOFINSTITUTIONS:
            converted = TenantType.INSTITUTION_GROUP;
            break;
        case GROUPOFSTATES:
            converted = TenantType.STATE_GROUP;
            break;
        case STATE:
            converted = TenantType.STATE;
            break;
        default:
            converted = null;
            break;
        }

        return converted;
    }
}

@Component
public class EligibilityEvaluatorImpl implements EligibilityEvaluator {

    private static final Logger LOGGER = LoggerFactory.getLogger(EligibilityEvaluatorImpl.class);

    private static final long PAGE_SIZE = 5000;

    @Autowired
    private EligibilityService eligibilityService;

    @Autowired
    private AssessmentRepository assessmentRepository;

    @Autowired
    private StudentRepository studentRepository;

    @Autowired
    private ExplicitEligibilityRepository explicitEligRepository;

    @Autowired
    private EligibilityEvaluatorCache eligibilityEvaluatorCache;

    @Autowired
    private Sb11EntityRepositoryService sb11EntityService;

    @Autowired
    private CacheMapService cacheMapService;

    @Autowired
    @Qualifier("eligibilityDependencyResolver")
    private EligibilityDependencyResolver eligibilityDependencyResolver;

    // change this class to be EligibilityEvaluator
    // use to evaluate explicit eligibility also
    // those rules don't need to be "run", but we do need to create EligibleAssessment/EligibleStudent from them
    // and like implicit eligibility, they only count if the assessment is in one of its test windows

    // implicit eligibility rules need to be evaluated under the following circumstances:
    // - student modification (upload or UI)
    // - accommodation modification (upload or UI)
    // - rule change
    // - assessment modified (change of eligibility type)

    // if a student is inserted or modified, the student must be checked against all
    // implicit rules on all assessments marked as implicit

    // if a student is deleted, then corresponding EligibleStudent must be deleted
    // and each EligibleAssessment that includes the student must be modified to remove the student

    // if accommodations are inserted or modified treat the same as student data insert or modification

    // if accommodations are deleted treat the same as student data modification

    // if the implicit eligibility rules for an assessment are changed (any create, update, delete)
    // then each student in the system must be tested against the rules

    // if an assessment changes from implicit to explicit eligibility,
    // the corresponding EligibleAssessment must be removed
    // and each EligibleStudent that includes the assessment must be modified to remove the assessment
    // then re-evaluate explicit eligibility to see if any records exist for the assessment

    // if an assessment changes from explicit to implicit eligibility,
    // the corresponding EligibleAssessment must be removed
    // and each EligibleStudent that includes the assessment must be modified to remove the assessment
    // then each student in the system must be checked against the new implicit rules

    @Override
    public void evaluateImplicitRules(final Assessment assessment, final Student student) {
        evaluateImplicitRules(assessment, true, student);
    }

    @ServiceActivator(inputChannel = "studentModificationEventsInbound")
    @Override
    public void evaluateStudentModification(final StudentModificationEvent event) {

        LOGGER.debug("Handling a StudentModificationEvent: " + event);
        switch (event.getAction()) {
        case UPD:
            processImplicitEligibilityForStudent(event.getSource());
            processExplicitEligibilityForStudent(event.getSource());
            break;
        case DEL:
            studentDeleted(event.getSource());
            break;
        default:
            // unknown, throw error here
            LOGGER.error("Unknown action: " + event.getAction());
            throw new EligibilityException("eligibility.unknown.event");
        }
    }

    @ServiceActivator(inputChannel = "batchStudentModificationEventsInbound")
    @Override
    public void evaluateBatchStudentModification(final BatchStudentModificationEvent event) {
        LOGGER.debug("Handling a BatchStudentModificationEvent: " + event);
        final List<Student> studentList = event.getSource();

        for (final Student student : studentList) {
            processImplicitEligibilityForStudent(student);
            processExplicitEligibilityForStudent(student);
        }
    }

    private void processImplicitEligibilityForStudent(final Student student) {
        // if a student is inserted or modified, the student must be checked against all
        // implicit rules on all assessments marked as implicit
        // remove EligibleStudent for this student
        // find all EligibleAssessments with this student id and modify to remove student
        // find all Assessments that use IMPLICIT eligibility and run all rules against this student
        this.eligibilityService.removeAllAssociationsForStudent(student);

        final List<Assessment> implicitAssessments = eligibilityEvaluatorCache
                .getImplicitAssessments(student.getInstitutionIdentifier(), student.getStateAbbreviation());
        for (final Assessment assess : implicitAssessments) {
            evaluateImplicitRules(assess, student);
        }
    }

    private void processExplicitEligibilityForStudent(final Student student) {

        // lookup explicit eligibility for this student
        // for all rows that exist, add to eligibleStudent
        final Student retrievedStudent = this.studentRepository
                .findByEntityIdAndStateAbbreviation(student.getEntityId(), student.getStateAbbreviation());

        final List<ExplicitEligibility> explicitEligibilities = this.explicitEligRepository
                .findByStudentIdAndStateAbbreviation(student.getEntityId(), student.getStateAbbreviation());

        if (!CollectionUtils.isEmpty(explicitEligibilities)) {
            for (final ExplicitEligibility explicitEligibility : explicitEligibilities) {
                Assessment assessment = eligibilityDependencyResolver
                        .resolveAssessmentForStudent(explicitEligibility);

                if (assessment != null) {
                    this.eligibilityService.saveAssociation(assessment, retrievedStudent);

                } else {
                    LOGGER.error("No assessment found for test name = " + explicitEligibility.getTestName()
                            + " and version = " + explicitEligibility.getTestVersion()
                            + " cannot set explicit eligibility for student id = " + student.getEntityId() + ".");
                }
            }

        }

    }

    private void studentDeleted(final Student student) {

        // if a student is deleted, then corresponding EligibleStudent must be deleted
        // and each EligibleAssessment that includes the student must be modified to remove the student

        // remove EligibleStudent for this student
        // find all EligibleAssessments with this student id and modify to remove student
        this.eligibilityService.removeAllAssociationsForStudent(student);
    }

    @ServiceActivator(inputChannel = "assessmentModificationEventsInbound")
    @Override
    public void evaluateAssessmentModification(final AssessmentModificationEvent event) {

        LOGGER.debug("Handling an AssessmentModificationEvent: " + event);

        switch (event.getAction()) {
        case UPD:
            assessmentModified(event);
            break;
        case DEL:
            assessmentDeleted(event);
            break;
        default:
            // unknown, throw error here
            LOGGER.error("Unknown action: " + event.getAction());
            throw new EligibilityException("eligibility.unknown.event");
        }
    }

    private void assessmentModified(final AssessmentModificationEvent event) {
        // if the implicit eligibility rules for an assessment are changed (any create, update, delete)
        // then each student in the system must be tested against the rules

        // if an assessment changes from implicit to explicit eligibility,
        // the corresponding EligibleAssessment must be removed
        // and each EligibleStudent that includes the assessment must be modified to remove the assessment
        // then re-evaluate explicit eligibility to see if any records exist for the assessment

        // if an assessment changes from explicit to implicit eligibility,
        // the corresponding EligibleAssessment must be removed
        // and each EligibleStudent that includes the assessment must be modified to remove the assessment
        // then each student in the system must be checked against the new implicit rules

        final Assessment source = event.getSource();

        LOGGER.debug("Removing all associations for assessment with id: " + source.getId());
        this.eligibilityService.removeAllAssociationsForAssessment(source);

        switch (source.getEligibilityType()) {
        case IMPLICIT:
            // this has potential to run really slow, but we should be able to filter by
            // the tenant on the assessment

            // find all applicable students
            // add filtering to find only applicable students, for now this will get ALL students
            // assessment has a tenant, convert tenant back into possible institution ids that can be used to
            // filter students

            final String tenantId = source.getTenantId();
            final Tenant tenant = this.eligibilityEvaluatorCache.getTenantById(tenantId);
            if (tenant == null) {
                break;
            }
            // get the entity that corresponds to the tenant

            final Sb11SuperEntity entity = this.sb11EntityService.findByEntityId(tenant.getName(),
                    tenant.getType());

            if (entity == null) {
                break;
            }
            final CacheMap cacheMap = this.cacheMapService.getCacheMap("UberEntityRelationshipMap");
            Map<String, Set<String>> uberMap = null;
            if (cacheMap != null) {
                uberMap = cacheMap.getMap();
            }

            final Set<String> validEntityMongoIds = uberMap.get(entity.getId());

            validEntityMongoIds.add(entity.getId());

            LOGGER.debug("Counting students in the DB");
            final long numStudents = this.studentRepository
                    .countByInstitutionFilter(new ArrayList<>(validEntityMongoIds));
            LOGGER.debug("Calculating implicit eligibility for " + numStudents + " students");

            long numPages = numStudents / PAGE_SIZE;
            final long remain = numStudents % PAGE_SIZE;

            if (remain > 0) {
                numPages++;
            }

            LOGGER.debug("Chunking students into " + numPages + " pages of size " + PAGE_SIZE);

            List<Student> students = null;
            List<Student> eligStudents = null;
            String studentMongoId = "000000000000000000000000";

            // TODO Another speedup can be obtained by sending each chunk to an @Async method, will have to see how
            // many executors is a good number

            for (long curPage = 0; curPage < numPages; curPage++) {
                final long timestamp = System.currentTimeMillis();
                // students = studentRepository.findAll(
                // new PageRequest((int) curPage, (int) PAGE_SIZE, new Sort("_id"))).getContent();
                students = this.studentRepository.findAllByRangeAndLimitWithInstitutionFilter(studentMongoId,
                        (int) PAGE_SIZE, new ArrayList<>(validEntityMongoIds));

                LOGGER.debug("Time to get another page of students: " + (System.currentTimeMillis() - timestamp));

                LOGGER.debug("Running rule evaluation for page " + curPage + " of " + numPages);
                LOGGER.debug("Evaluating " + students.size() + " students");

                studentMongoId = students.get(students.size() - 1).getId();

                final long timestamp2 = System.currentTimeMillis();
                eligStudents = evaluateImplicitRules(source, false, Iterables.toArray(students, Student.class));
                LOGGER.debug("Time to run rule eval for chunk of students: "
                        + (System.currentTimeMillis() - timestamp2));

                LOGGER.debug("Evaluation found " + eligStudents.size() + " eligible students, calling save");

                final long timestamp3 = System.currentTimeMillis();

                if (!eligStudents.isEmpty()) {
                    this.eligibilityService.saveAssociations(source, eligStudents);
                }

                LOGGER.debug("Time to save new EligibleStudents for one chunk of students: "
                        + (System.currentTimeMillis() - timestamp3));
                LOGGER.debug("Time to run one chunk of evaluation rules plus save: "
                        + (System.currentTimeMillis() - timestamp));
            }

            break;

        case EXPLICIT:
            // search for any explicit eligibility records for this assessment and associate
            final List<ExplicitEligibility> eligRecs = this.explicitEligRepository
                    .findByTestNameAndTestVersion(source.getTestName(), source.getVersion());

            final List<Student> tmpStudents = new ArrayList<>();

            for (final ExplicitEligibility explicitElig : eligRecs) {
                // look up Student
                tmpStudents.add(this.studentRepository.findByEntityIdAndStateAbbreviation(
                        explicitElig.getStudentId(), explicitElig.getStateAbbreviation()));
            }

            if (!tmpStudents.isEmpty()) {
                this.eligibilityService.saveAssociations(source, tmpStudents);
            }

            break;

        default:
            LOGGER.error("Unknown eligbility type: " + source.getEligibilityType());
            throw new EligibilityException("eligibility.unknown.type");
        }
    }

    private void assessmentDeleted(final AssessmentModificationEvent event) {
        this.eligibilityService.removeAllAssociationsForAssessment(event.getSource());
    }

    @ServiceActivator(inputChannel = "explicitEligibilityModificationEventsInbound")
    @Override
    public void evaluateExplicitEligibilityModification(final ExplicitEligibilityModificationEvent event) {

        LOGGER.debug("Handling an ExplicitEligibilityModificationEvent: " + event);

        switch (event.getAction()) {
        case UPD:
            explicitEligibilityModified(event);
            break;
        case DEL:
            explicitEligibilityDeleted(event);
            break;
        default:
            // unknown, throw error here
            LOGGER.error("Unknown action: " + event.getAction());
            throw new EligibilityException("eligibility.unknown.event");
        }
    }

    private void explicitEligibilityModified(final ExplicitEligibilityModificationEvent event) {
        // this is really just ADD, there is no update of explicit eligibility
        // find the student by student id
        // find the assessment by alternate key
        final ExplicitEligibility explicitEligibility = event.getSource();

        final Student student = this.studentRepository.findByEntityIdAndStateAbbreviation(
                explicitEligibility.getStudentId(), event.getSource().getStateAbbreviation());
        Assessment assessment = eligibilityDependencyResolver.resolveAssessmentForStudent(explicitEligibility);

        // associate the two
        this.eligibilityService.saveAssociation(assessment, student);
    }

    private void explicitEligibilityDeleted(final ExplicitEligibilityModificationEvent event) {
        // find the student by student id
        // find the assessment by alternate key

        final Student student = this.studentRepository.findByEntityIdAndStateAbbreviation(
                event.getSource().getStudentId(), event.getSource().getStateAbbreviation());
        Assessment assessment = eligibilityDependencyResolver.resolveAssessmentForStudent(event.getSource());

        // remove the association
        this.eligibilityService.removeAssociation(assessment, student);
    }

    @Override
    public void recalculateAllEligibility() {

        // remove the eligible student collection
        this.eligibilityService.removeEligibleStudentCollection();

        // load all the Assessments in the system and create assessment modified events for each
        // loop and call assessmentModified() for each

        final List<Assessment> allAssessments = this.assessmentRepository.findAll();

        final List<AssessmentModificationEvent> events = Lists.transform(allAssessments,
                ARTHelpers.ASSESSMENT_TRANSFORMER);

        for (final AssessmentModificationEvent event : events) {
            try {
                evaluateAssessmentModification(event);
            } catch (final Exception e) {
                LOGGER.warn("Caught exception while re-evaluating all eligibility, ignoring: ", e);
            }
        }

        // done!
    }

    private List<Student> evaluateImplicitRules(final Assessment assessment, final boolean shouldSave,
            final Student... students) {
        final List<Student> eligibleStudents = Lists.newArrayList();

        final List<ImplicitEligibilityRule> enablerRules = assessment.getEnablerRules();
        final List<ImplicitEligibilityRule> disablerRules = assessment.getDisablerRules();

        boolean disabled = false;
        boolean enabled = false;

        for (final Student student : students) {
            disabled = false;
            enabled = false;

            if (!CollectionUtils.isEmpty(disablerRules)) {
                disabled = true;
                // run disabler rules first
                for (final ImplicitEligibilityRule rule : disablerRules) {
                    if (!(disabled = applyRule(assessment.getSubjectCode(), rule, student, rule.getField()))) {
                        break;
                    }
                }
            }

            if (!disabled) {
                // run enabler rules
                if (!CollectionUtils.isEmpty(enablerRules)) {
                    enabled = true;
                    for (final ImplicitEligibilityRule rule : enablerRules) {
                        if (!(enabled = applyRule(assessment.getSubjectCode(), rule, student, rule.getField()))) {
                            break;
                        }
                    }
                }
            }

            if (!disabled && enabled) {
                eligibleStudents.add(student);
            }
        }
        if (shouldSave && !CollectionUtils.isEmpty(eligibleStudents)) {
            // students that are eligible for the assessment, make the associations
            this.eligibilityService.saveAssociations(assessment, eligibleStudents);
        }

        return eligibleStudents;
    }

    private boolean applyRule(final String assessmentSubjectCode, final ImplicitEligibilityRule rule,
            final Student student, final String fieldName) {
        Object reflectedValue = null;
        try {
            reflectedValue = PropertyUtils.getProperty(student, fieldName);
            if (reflectedValue == null || !evaluateOperator(reflectedValue, rule)) {
                return false;
            }
        } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
            LOGGER.debug("Field '" + fieldName + "' is not on Student, looking at Accommodations");
            final Map<String, Object> accommodation = student.getAccommodation(assessmentSubjectCode);
            if (accommodation != null) {
                try {
                    reflectedValue = accommodation.get(fieldName);
                } catch (Exception e1) {
                    LOGGER.error("Failure in eligibility eval can't find field called " + fieldName
                            + " in accommodation, invalid rule", e1);
                    throw new EligibilityException("eligibility.invalid.compare.field", new String[] { fieldName },
                            e);
                }
            } else {
                LOGGER.debug("Accommodation not found for assessment with subject " + assessmentSubjectCode
                        + " skipping getProperty");
            }

            if (reflectedValue == null) {
                return false;
            } else {
                if (reflectedValue instanceof ArrayList) {
                    ArrayList<?> valueList = (ArrayList<?>) reflectedValue;
                    for (Object value : valueList) {
                        if (evaluateOperator(value, rule) == true) {
                            return true;
                        }
                    }
                    return false;
                } else {
                    if (!evaluateOperator(reflectedValue, rule)) {
                        return false;
                    }
                }
            }
        }
        return true;
    }

    @SuppressWarnings("unchecked")
    private boolean evaluateOperator(final Object value, final ImplicitEligibilityRule rule) {

        if (rule.getOperatorType().isValidFor(value.getClass())) {
            switch (rule.getOperatorType()) {
            case EQUALS:
                return value.equals(convertTo(rule.getValue(), value.getClass()));
            case GREATER_THAN:
                return ((Comparable<Object>) value).compareTo(convertTo(rule.getValue(), value.getClass())) > 0;
            case GREATER_THAN_EQUALS:
                return ((Comparable<Object>) value).compareTo(convertTo(rule.getValue(), value.getClass())) >= 0;
            case LESS_THAN:
                return ((Comparable<Object>) value).compareTo(convertTo(rule.getValue(), value.getClass())) < 0;
            case LESS_THAN_EQUALS:
                return ((Comparable<Object>) value).compareTo(convertTo(rule.getValue(), value.getClass())) <= 0;
            default:
                LOGGER.error("The class type " + value.getClass().toString()
                        + " cannot be compared using the operator " + rule.getOperatorType().name());
                throw new EligibilityException("eligiblity.invalid.operator.forclass",
                        new String[] { value.getClass().toString(), rule.getOperatorType().name() });
            }
        } else {
            LOGGER.error("The class type " + value.getClass().toString() + " cannot be compared using the operator "
                    + rule.getOperatorType().name());
            throw new EligibilityException("eligiblity.invalid.operator.forclass",
                    new String[] { value.getClass().toString(), rule.getOperatorType().name() });
        }
    }

    @SuppressWarnings("unchecked")
    private <T> T convertTo(final String value, final Class<T> clazz) {

        try {
            if (String.class.isAssignableFrom(clazz)) {
                return (T) value;
            } else if (Number.class.isAssignableFrom(clazz)) {
                return clazz.getConstructor(String.class).newInstance(value);
            } else if (DateTime.class.isAssignableFrom(clazz)) {
                DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
                DateTime dateTime = formatter.parseDateTime(value);
                return (T) dateTime;
            } else if (Enum.class.isAssignableFrom(clazz)) {
                Method getEnum = null;
                try {
                    getEnum = clazz.getDeclaredMethod("getEnumByValue", String.class);
                } catch (final NoSuchMethodException me) {
                    getEnum = clazz.getMethod("valueOf", String.class);
                }
                return (T) getEnum.invoke(null, value);
            } else {
                LOGGER.error("Failure to convert value \"" + value + "\" into class type " + clazz.toString());
                throw new EligibilityException("eligibility.value.convert.error",
                        new String[] { value, clazz.toString() });
            }
        } catch (InstantiationException | IllegalAccessException | IllegalArgumentException
                | InvocationTargetException | NoSuchMethodException | SecurityException e) {
            LOGGER.error("Failure to convert value \"" + value + "\" into class type " + clazz.toString(), e);
            throw new EligibilityException("eligibility.value.convert.error",
                    new String[] { value, clazz.toString() }, e);
        }
    }

}