org.kuali.coeus.common.questionnaire.impl.question.QuestionMaintenanceDocumentRule.java Source code

Java tutorial

Introduction

Here is the source code for org.kuali.coeus.common.questionnaire.impl.question.QuestionMaintenanceDocumentRule.java

Source

/*
 * Kuali Coeus, a comprehensive research administration system for higher education.
 * 
 * Copyright 2005-2015 Kuali, Inc.
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero 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 Affero General Public License for more details.
 * 
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.kuali.coeus.common.questionnaire.impl.question;

import com.google.common.collect.Lists;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.kuali.coeus.common.framework.custom.attr.CustomAttributeService;
import org.kuali.coeus.common.questionnaire.framework.question.Question;
import org.kuali.coeus.common.questionnaire.framework.question.QuestionExplanation;
import org.kuali.coeus.common.questionnaire.framework.question.QuestionMultiChoice;
import org.kuali.coeus.sys.framework.rule.KcMaintenanceDocumentRuleBase;
import org.kuali.coeus.sys.framework.service.KcServiceLocator;
import org.kuali.kra.infrastructure.Constants;
import org.kuali.kra.infrastructure.KeyConstants;
import org.kuali.rice.kns.document.MaintenanceDocument;
import org.kuali.rice.kns.maintenance.Maintainable;
import org.kuali.rice.krad.bo.PersistableBusinessObject;
import org.kuali.rice.krad.exception.ValidationException;
import org.kuali.rice.krad.util.GlobalVariables;

import java.util.Collection;
import java.util.List;

/**
 * This class contains the business rules that are specific to Question.
 */
public class QuestionMaintenanceDocumentRule extends KcMaintenanceDocumentRuleBase {

    private static final Log LOG = LogFactory.getLog(QuestionMaintenanceDocumentRule.class);

    public QuestionMaintenanceDocumentRule() {
        super();
    }

    protected Collection<Class<?>> relationshipDeleteVerificationIgnores() {
        return Lists.<Class<?>>newArrayList(QuestionMultiChoice.class, QuestionExplanation.class);
    }

    /**
     * 
     * This method executes the DataDictionary Validation against the document.
     * It's an exact replica of MaintenanceDocumentRuleBase with the exception of the
     * error path being "document.newMaintainableObject.businessObject" instead of 
     * "document.newMaintainableObject". 
     * TODO: Find a better solution as this duplicates code and is prone to failure if
     *       the rice framework changes, specifically its dataDictionaryValidate method.
     * @param document
     * @return true if it passes DD validation, false otherwise
     */
    @Override
    protected boolean dataDictionaryValidate(MaintenanceDocument document) {
        LOG.debug("MaintenanceDocument validation beginning");

        // explicitly put the errorPath that the dictionaryValidationService requires
        GlobalVariables.getMessageMap().addToErrorPath("document.newMaintainableObject.businessObject");
        try {
            // document must have a newMaintainable object
            Maintainable newMaintainable = document.getNewMaintainableObject();
            if (newMaintainable == null) {
                GlobalVariables.getMessageMap().removeFromErrorPath("document.newMaintainableObject");
                throw new ValidationException("Maintainable object from Maintenance Document '"
                        + document.getDocumentTitle() + "' is null, unable to proceed.");
            }

            // document's newMaintainable must contain an object (ie, not null)
            PersistableBusinessObject businessObject = newMaintainable.getBusinessObject();
            if (businessObject == null) {
                GlobalVariables.getMessageMap().removeFromErrorPath("document.newMaintainableObject.");
                throw new ValidationException("Maintainable's component business object is null.");
            }

            // run required check from maintenance data dictionary
            maintDocDictionaryService.validateMaintenanceRequiredFields(document);

            //check for duplicate entries in collections if necessary
            maintDocDictionaryService.validateMaintainableCollectionsForDuplicateEntries(document);

            // run the DD DictionaryValidation (non-recursive)
            dictionaryValidationService.validateBusinessObject(businessObject, false);

            // do default (ie, mandatory) existence checks
            dictionaryValidationService.validateDefaultExistenceChecks(businessObject);
        } finally {
            // explicitly remove the errorPath we've added
            GlobalVariables.getMessageMap().removeFromErrorPath("document.newMaintainableObject.businessObject");
        }

        LOG.debug("MaintenanceDocument validation ending");
        return true;
    }

    /**
     * This method validates the user entered data of a Question when the document is routed.
     * @param maintenanceDocument
     * @return true if valid, false otherwise
     */
    @Override
    protected boolean processCustomRouteDocumentBusinessRules(MaintenanceDocument maintenanceDocument) {
        boolean isValid = true;

        isValid &= validateQuestionUsage(maintenanceDocument);
        isValid &= validateQuestionResponseType(maintenanceDocument);

        return isValid;
    }

    /**
     * This method validates the question status.  The status can not be inactive when a questionnaire is
     * using the question.
     * @param maintenanceDocument - the maintenance document of the question to be validated
     * @return true if all validation has passed, false otherwise
     */
    private boolean validateQuestionUsage(MaintenanceDocument maintenanceDocument) {
        Question question = (Question) maintenanceDocument.getNewMaintainableObject().getDataObject();

        if (!"A".equals(question.getStatus()) && getQuestionService().isQuestionUsed(question.getQuestionSeqId())) {
            GlobalVariables.getMessageMap().putError(Constants.QUESTION_DOCUMENT_FIELD_STATUS,
                    KeyConstants.ERROR_QUESTION_STATUS_IN_USE);
            return false;
        } else {
            return true;
        }
    }

    /**
     * This method validates the question response type and any additional properties related to the response type.
     * @param maintenanceDocument - the maintenance document of the question to be validated
     * @return true if all validation has passed, false otherwise
     */
    private boolean validateQuestionResponseType(MaintenanceDocument maintenanceDocument) {
        boolean isValid = true;

        Question question = (Question) maintenanceDocument.getNewMaintainableObject().getDataObject();

        if (question.getQuestionTypeId() == null) {
            isValid &= false;
            GlobalVariables.getMessageMap().putError(Constants.QUESTION_DOCUMENT_FIELD_QUESTION_TYPE_ID,
                    KeyConstants.ERROR_QUESTION_RESPONSE_TYPE_NOT_SPECIFIED);
        } else {
            switch (question.getQuestionTypeId().intValue()) {
            case (int) Constants.QUESTION_RESPONSE_TYPE_YES_NO:
                isValid &= validateResponseTypeYesNo(question);
                break;
            case (int) Constants.QUESTION_RESPONSE_TYPE_YES_NO_NA:
                isValid &= validateResponseTypeYesNoNa(question);
                break;
            case (int) Constants.QUESTION_RESPONSE_TYPE_NUMBER:
                isValid &= validateResponseTypeNumber(question);
                break;
            case (int) Constants.QUESTION_RESPONSE_TYPE_DATE:
                isValid &= validateResponseTypeDate(question);
                break;
            case (int) Constants.QUESTION_RESPONSE_TYPE_TEXT:
                isValid &= validateResponseTypeText(question);
                break;
            case (int) Constants.QUESTION_RESPONSE_TYPE_LOOKUP:
                isValid &= validateResponseTypeLookup(question);
                break;
            case (int) Constants.QUESTION_RESPONSE_TYPE_MULTIPLE_CHOICE:
                isValid &= validateResponseTypeMultipleChoice(question);
                break;
            default:
                isValid &= false;
                GlobalVariables.getMessageMap().putError(Constants.QUESTION_DOCUMENT_FIELD_QUESTION_TYPE_ID,
                        KeyConstants.ERROR_QUESTION_RESPONSE_TYPE_INVALID);
                break;
            }
        }

        return isValid;
    }

    /**
     * This method validates the additional properties of a Yes/No response to a question.
     * Since no additional properties exist this method always returns true.
     * @param question - the question to be validated
     * @return true
     */
    private boolean validateResponseTypeYesNo(Question question) {
        return true;
    }

    /**
     * This method validates the additional properties of a Yes/No/NA response to a question.
     * Since no additional properties exist this method always returns true.
     * @param question - the question to be validated
     * @return true
     */
    private boolean validateResponseTypeYesNoNa(Question question) {
        return true;
    }

    /**
     * This method validates the additional properties of a numeric response to a question.
     * @param question - the question to be validated
     * @return true if all validation has passed, false otherwise
     */
    private boolean validateResponseTypeNumber(Question question) {
        boolean isValid = true;

        isValid &= validateDisplayedAnswers(question);
        isValid &= validateAnswerMaxLengthWithCeiling(question);
        isValid &= validateMaxAnswers(question);

        return isValid;
    }

    /**
     * This method validates the additional properties of a date response to a question.
     * @param question - the question to be validated
     * @return true if all validation has passed, false otherwise
     */
    private boolean validateResponseTypeDate(Question question) {
        boolean isValid = true;

        isValid &= validateDisplayedAnswers(question);
        isValid &= validateMaxAnswers(question);

        return isValid;
    }

    /**
     * This method validates the additional properties of a text response to a question.
     * @param question - the question to be validated
     * @return true if all validation has passed, false otherwise
     */
    private boolean validateResponseTypeText(Question question) {
        boolean isValid = true;

        isValid &= validateDisplayedAnswers(question);
        isValid &= validateAnswerMaxLengthWithCeiling(question);
        isValid &= validateMaxAnswers(question);

        return isValid;
    }

    /**
     * This method validates the additional properties of a lookup response to a question.
     * @param question - the question to be validated
     * @return true if all validation has passed, false otherwise
     */
    private boolean validateResponseTypeLookup(Question question) {
        boolean isValid = true;

        isValid &= validateLookupClass(question);
        isValid &= validateLookupReturn(question);
        isValid &= validateMaxAnswers(question);

        return isValid;
    }

    private boolean validateResponseTypeMultipleChoice(Question question) {
        boolean isValid = true;

        isValid &= validateDisplayedAnswers(question);
        isValid &= validateMaxAnswers(question);
        isValid &= validateMultipleChoiceOptions(question);
        return isValid;
    }

    private boolean validateMultipleChoiceOptions(Question question) {
        boolean isValid = true;

        if (CollectionUtils.isEmpty(question.getQuestionMultiChoices()) || question.getDisplayedAnswers() <= 0) {
            isValid = false;
        }

        for (int i = 0; i < question.getQuestionMultiChoices().size(); i++) {
            GlobalVariables.getMessageMap().addToErrorPath(
                    "document.newMaintainableObject.businessObject.questionMultiChoices[" + i + "]");
            try {
                dictionaryValidationService.validate(question.getQuestionMultiChoices().get(i));
            } finally {
                GlobalVariables.getMessageMap().removeFromErrorPath(
                        "document.newMaintainableObject.businessObject.questionMultiChoices[" + i + "]");
            }
        }

        return isValid;
    }

    /**
     * This method validates the displayedAnswers field.  The field must contain a number greater than zero.
     * @param question - the question to be validated
     * @return true if all validation has passed, false otherwise
     */
    private boolean validateDisplayedAnswers(Question question) {
        if (question.getDisplayedAnswers() != null && question.getDisplayedAnswers() > 0) {
            return true;
        }

        if (Long.valueOf(Constants.QUESTION_RESPONSE_TYPE_TEXT).equals(question.getQuestionTypeId())) {
            GlobalVariables.getMessageMap().putError(Constants.QUESTION_DOCUMENT_FIELD_DISPLAYED_ANSWERS,
                    KeyConstants.ERROR_QUESTION_DISPLAYED_ANSWERS_INVALID_AREAS);
            return false;
        } else if (!Long.valueOf(Constants.QUESTION_RESPONSE_TYPE_MULTIPLE_CHOICE)
                .equals(question.getQuestionTypeId())) {
            GlobalVariables.getMessageMap().putError(Constants.QUESTION_DOCUMENT_FIELD_DISPLAYED_ANSWERS,
                    KeyConstants.ERROR_QUESTION_DISPLAYED_ANSWERS_INVALID_BOXES);
            return false;
        }

        return true;
    }

    /**
     * This method validates the answerMaxLength field.  The field must contain a number greater than zero.
     * @param question - the question to be validated
     * @return true if all validation has passed, false otherwise
     */
    private boolean validateAnswerMaxLength(Question question) {
        if (question.getAnswerMaxLength() != null && question.getAnswerMaxLength() > 0) {
            return true;
        } else {
            GlobalVariables.getMessageMap().putError(Constants.QUESTION_DOCUMENT_FIELD_ANSWER_MAX_LENGTH,
                    KeyConstants.ERROR_QUESTION_ANSWER_MAX_LENGTH_INVALID);
            return false;
        }
    }

    public boolean validateAnswerMaxLengthWithCeiling(Question question) {
        if (validateAnswerMaxLength(question)) {
            if (question.getAnswerMaxLength() != null && question.getAnswerMaxLength() <= 2000) {
                return true;
            } else {
                GlobalVariables.getMessageMap().putError(Constants.QUESTION_DOCUMENT_FIELD_ANSWER_MAX_LENGTH,
                        KeyConstants.ERROR_QUESTION_ANSWER_MAX_LENGTH_VALUE_TOO_LARGE);
                return false;
            }
        } else {
            return false;
        }
    }

    /**
     * This method validates the maxAnswers field.  The field must contain a number greater than zero.
     * @param question - the question to be validated
     * @return true if all validation has passed, false otherwise
     */
    private boolean validateMaxAnswers(Question question) {
        boolean isValid = true;

        if (question.getMaxAnswers() == null || question.getMaxAnswers() <= 0) {
            isValid = false;
        }
        if (question.getQuestionTypeId() != Constants.QUESTION_RESPONSE_TYPE_LOOKUP
                && question.getMaxAnswers() != null && question.getDisplayedAnswers() != null
                && question.getMaxAnswers() > question.getDisplayedAnswers()) {
            isValid = false;
        }

        if (!isValid) {
            switch (question.getQuestionTypeId().intValue()) {
            case (int) Constants.QUESTION_RESPONSE_TYPE_LOOKUP:
                GlobalVariables.getMessageMap().putError(Constants.QUESTION_DOCUMENT_FIELD_MAX_ANSWERS,
                        KeyConstants.ERROR_QUESTION_MAX_ANSWERS_INVALID_RETURNS);
                break;
            case (int) Constants.QUESTION_RESPONSE_TYPE_TEXT:
                GlobalVariables.getMessageMap().putError(Constants.QUESTION_DOCUMENT_FIELD_MAX_ANSWERS,
                        KeyConstants.ERROR_QUESTION_MAX_ANSWERS_INVALID_ANSWERS_AREAS);
                break;
            case (int) Constants.QUESTION_RESPONSE_TYPE_MULTIPLE_CHOICE:
                GlobalVariables.getMessageMap().putError(Constants.QUESTION_DOCUMENT_FIELD_MAX_ANSWERS,
                        KeyConstants.ERROR_QUESTION_MAX_ANSWERS_INVALID_ANSWERS_CHECKBOXES);
                break;
            default:
                GlobalVariables.getMessageMap().putError(Constants.QUESTION_DOCUMENT_FIELD_MAX_ANSWERS,
                        KeyConstants.ERROR_QUESTION_MAX_ANSWERS_INVALID_ANSWERS_BOXES);
                break;
            }
        }

        return isValid;
    }

    /**
     * This method validates the lookupClass field.  The field must may not contain null.
     * @param question - the question to be validated
     * @return true if all validation has passed, false otherwise
     */
    private boolean validateLookupClass(Question question) {
        // Force a reload the lookupReturn dropdown list when the lookupClass changes 
        String prevLookupClass = (String) GlobalVariables.getUserSession()
                .retrieveObject(Constants.LOOKUP_CLASS_NAME);
        if (ObjectUtils.equals(question.getLookupClass(), prevLookupClass)) {
            GlobalVariables.getUserSession().removeObject(Constants.LOOKUP_RETURN_FIELDS);
        }

        if (question.getLookupClass() != null) {
            return true;
        } else {
            GlobalVariables.getMessageMap().putError(Constants.QUESTION_DOCUMENT_FIELD_LOOKUP_CLASS,
                    KeyConstants.ERROR_QUESTION_LOOKUP_CLASS_NOT_SPECIFIED);
            return false;
        }
    }

    /**
     * This method validates the lookupReturn field.  The field must may not contain null.
     * @param question - the question to be validated
     * @return true if all validation has passed, false otherwise
     */
    private boolean validateLookupReturn(Question question) {
        if (question.getLookupReturn() != null) {
            return validateLookupReturnBasedOnLookupClass(question);
        } else {
            GlobalVariables.getMessageMap().putError(Constants.QUESTION_DOCUMENT_FIELD_LOOKUP_RETURN,
                    KeyConstants.ERROR_QUESTION_LOOKUP_RETURN_NOT_SPECIFIED);
            return false;
        }
    }

    /**
     * This method validates the lookupReturn with the specified lookupClass of the question.
     * (The lookupReturn may be different if JavaScript is disabled and thus the automatic population
     *  of the lookupReturn drop down list has not occurred after the lookupClass has changed.) 
     * @param question - the question to be validated
     * @return true if validation has passed, false otherwise
     */
    @SuppressWarnings("unchecked")
    private boolean validateLookupReturnBasedOnLookupClass(Question question) {
        if (question.getLookupClass() != null) {
            try {
                List<String> lookupReturnClasses = getCustomAttributeService()
                        .getLookupReturns(question.getLookupClass());
                for (String lookupReturnClass : lookupReturnClasses) {
                    if (StringUtils.equals(lookupReturnClass, question.getLookupReturn())) {
                        return true;
                    }
                }
                GlobalVariables.getMessageMap().putError(Constants.QUESTION_DOCUMENT_FIELD_LOOKUP_RETURN,
                        KeyConstants.ERROR_QUESTION_LOOKUP_RETURN_INVALID);
                return false;
            } catch (Exception e) {
                LOG.info(e.getMessage());
                throw new RuntimeException("QuestionMaintenanceDocumentRule encountered exception", e);
            }
        }
        return true;
    }

    private CustomAttributeService getCustomAttributeService() {
        return KcServiceLocator.getService(CustomAttributeService.class);
    }

    private QuestionService getQuestionService() {
        return KcServiceLocator.getService(QuestionService.class);
    }

}