tds.itemscoringengine.itemscorers.QTIRubricScorer.java Source code

Java tutorial

Introduction

Here is the source code for tds.itemscoringengine.itemscorers.QTIRubricScorer.java

Source

/*******************************************************************************
 * Educational Online Test Delivery System Copyright (c) 2014 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 tds.itemscoringengine.itemscorers;

import java.io.IOException;
import java.io.StringReader;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.Predicate;
import org.apache.commons.collections.Transformer;
import org.apache.commons.lang3.StringUtils;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.JDOMException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import qtiscoringengine.QTIRubric;
import qtiscoringengine.ValidationLog;
import AIR.Common.Helpers._Ref;
import AIR.Common.Utilities.JavaPrimitiveUtils;
import AIR.Common.xml.XmlElement;
import AIR.Common.xml.XmlReader;
import AIR.Common.xml.XmlReaderException;
import tds.itemscoringengine.ItemScore;
import tds.itemscoringengine.ItemScoreInfo;
import tds.itemscoringengine.ResponseInfo;
import tds.itemscoringengine.RubricContentType;
import tds.itemscoringengine.RubricStats;
import tds.itemscoringengine.ScoreRationale;
import tds.itemscoringengine.ScoringStatus;
import tds.itemscoringengine.VarBinding;
import tds.itemscoringengine.complexitemscorers.IRubric;
import tds.itemscoringengine.complexitemscorers.IRubricStats;

public class QTIRubricScorer implements IRubric {
    private static final Logger _logger = LoggerFactory.getLogger(QTIRubricScorer.class);
    private String itemId;
    private IRubricStats stats = new RubricStats();
    private QTIRubric _rubric = null;

    private String _invalidRubricMessage = null;
    private Exception _exception = null;

    public QTIRubricScorer(String itemId) {
        this.itemId = itemId;
    }

    @Override
    public String getType() {
        // TODO Auto-generated method stub
        return null;
    }

    public String getItemId() {
        return this.itemId;
    }

    public IRubricStats getStats() {
        return stats;
    }

    public boolean wasThereErrorWithRubric() {
        if (StringUtils.isEmpty(_invalidRubricMessage) && _exception == null)
            return false;
        return true;
    }

    public String getErrorMessageIfAny() {
        return _invalidRubricMessage;
    }

    public void load(RubricContentType type, Object rubric) {
        try {
            // now go through and create the rubric and score the response
            XmlReader reader = getReader(type, rubric);
            ValidationLog log = new ValidationLog(type.toString());
            _rubric = QTIRubric.fromXML(type.toString(), reader, log);

            // check if there were any problems with the rubric
            if (_rubric == null || !_rubric.validate(log)) {

                StringBuilder rationaleString = new StringBuilder();
                for (int i = 0; i < log.getCount(); i++)
                    rationaleString.append(log.Message(i) + "\n");
                _invalidRubricMessage = rationaleString.toString();

                _logger.error(String.format("Error validating rubric. File %s. Message %s", rubric.toString(),
                        rationaleString.toString()));
            }
        } catch (Exception e) {
            _exception = e;
            e.printStackTrace();
            _logger.error("Error : " + e.getMessage());
            _invalidRubricMessage = _invalidRubricMessage + "\n" + "Error processing rubric. Message: "
                    + e.getMessage();
        }
    }

    public ItemScore scoreItem(ResponseInfo responseInfo) {
        ItemScore score = new ItemScore(-1, -1, ScoringStatus.NotScored, "overall", new ScoreRationale(),
                responseInfo.getContextToken()); // We
        // Shiva: I added the follow null check on the response as it makes it
        // easier to test it on the web
        // interface with a REST client.
        String response = responseInfo.getStudentResponse();
        if (response != null) {
            response = response.trim();
            responseInfo.setStudentResponse(response);
        }

        // Max
        // score
        // is
        long startTime = System.currentTimeMillis();
        Map<String, String> identifiersAndResponses = new HashMap<String, String>();
        // first try to retrieve the item response, and the identifier
        try {
            XmlReader reader = new XmlReader(new StringReader(responseInfo.getStudentResponse()));
            Document doc = reader.getDocument();

            List<Element> responseNodes = new XmlElement(doc.getRootElement())
                    .selectNodes("//itemResponse/response");
            for (Element elem : responseNodes) {
                String identifier = elem.getAttribute("id").getValue();
                List<String> responses = new ArrayList<String>();
                List<Element> valueNodes = new XmlElement(elem).selectNodes("value");
                for (Element valElem : valueNodes) {
                    responses.add(new XmlElement(valElem).getInnerText());
                }

                identifiersAndResponses.put(identifier, StringUtils.join(responses, ","));
            }

        } catch (final Exception e) {
            e.printStackTrace();
            _logger.error("Error loading responses " + e.getMessage());
            score.getScoreInfo().setStatus(ScoringStatus.ScoringError);
            score.getScoreInfo().getRationale().setMsg("Error loading response. Message: " + e.getMessage());
            score.getScoreInfo().getRationale().setException(e);
            return score;
        }

        if (identifiersAndResponses.size() == 0) {
            // This could be a response from a grid/ti/eq item being scored using a
            // QTI version of our proprietary rubrics
            // Check if this is a TI/GI/SIM/EQ and unfortunately, there is no clean
            // way to figure this out other than resorting to this hack
            response = responseInfo.getStudentResponse();

            if (!StringUtils.isEmpty(response) && ((response.toUpperCase().startsWith("<RESPONSESPEC>")
                    || response.toUpperCase().startsWith("<RESPONSETABLE>")) || // TI
                    (response.toUpperCase().startsWith("<RESPONSE>")) || // EQ
                    (response.toUpperCase().contains("<ANSWERSET>"))) // GI
            ) {
                identifiersAndResponses.put("RESPONSE", responseInfo.getStudentResponse());
            } else {
                _logger.error("No responses found");
                score.getScoreInfo().getRationale().setMsg("No responses found");
                return score;
            }
        }

        try {
            if (wasThereErrorWithRubric()) {
                _logger.error(String.format("Error validating rubric. File %s. Message %s",
                        responseInfo.getRubric().toString(), getErrorMessageIfAny()));
                score.getScoreInfo().getRationale().setMsg(getErrorMessageIfAny());
                return score;
            }

            // now score the item
            String[] scoreArr = null;
            List<String[]> bindings = _rubric.evaluate(identifiersAndResponses);
            for (String[] binding : bindings) {
                if ("score".equalsIgnoreCase(binding[0]))
                    scoreArr = binding;
            }

            // error check the score result
            if (scoreArr == null) {
                score.getScoreInfo().setStatus(ScoringStatus.ScoringError);
                score.getScoreInfo().getRationale()
                        .setMsg("There was no SCORE identifier specified for the identifiers "
                                + StringUtils.join(identifiersAndResponses.keySet(), ','));
                return score;
            }
            if (scoreArr.length < 4 || StringUtils.isEmpty(scoreArr[3])) {
                score.getScoreInfo().setStatus(ScoringStatus.ScoringError);
                score.getScoreInfo().getRationale().setMsg("There was no score specified for the identifiers "
                        + StringUtils.join(identifiersAndResponses.keySet(), ','));
                return score;
            }

            // try to parse the actual score value -- note the score value is
            // hardcoded to be at index 3
            _Ref<Double> dScore = new _Ref<>();
            if (JavaPrimitiveUtils.doubleTryParse(scoreArr[3], dScore)) {
                // note: we truncate the double score to be an int
                score.getScoreInfo().setPoints((int) Math.ceil(Math.max(dScore.get(), 0)));
                score.getScoreInfo().setStatus(ScoringStatus.Scored);
                score.getScoreInfo().getRationale().setMsg("successfully scored");

                // populate the requested outgoing bindings
                if (responseInfo.getOutgoingBindings() != null) {
                    if (responseInfo.getOutgoingBindings().contains(VarBinding.ALL)) // All
                                                                                     // bindings
                                                                                     // requested
                    {
                        score.getScoreInfo().getRationale().setBindings(new ArrayList<VarBinding>());

                        CollectionUtils.collect(bindings, new Transformer() {

                            @Override
                            public Object transform(Object arg0) {
                                final String[] variable = (String[]) arg0;
                                return new VarBinding() {
                                    {
                                        setName(variable[0]);
                                        setType(variable[1]);
                                        setValue(variable[3]);
                                    }
                                };
                            }
                        }, score.getScoreInfo().getRationale().getBindings());

                    } else // Specific bindings requested
                    {
                        score.getScoreInfo().getRationale().setBindings(new ArrayList<VarBinding>());
                        for (final VarBinding outgoingBinding : responseInfo.getOutgoingBindings()) {

                            final String[] binding = (String[]) CollectionUtils.find(bindings, new Predicate() {

                                @Override
                                public boolean evaluate(Object arg0) {
                                    return StringUtils.equals(((String[]) arg0)[0], outgoingBinding.getName());
                                }
                            });

                            if (binding != null)
                                score.getScoreInfo().getRationale().getBindings().add(new VarBinding() {
                                    {
                                        setName(binding[0]);
                                        setType(binding[1]);
                                        setValue(binding[3]);
                                    }
                                });

                        }
                    }
                }

                // populate any subscores that might have been recorded as internal
                // scoring state
                for (Object stateObj : _rubric.getResponseProcessingState()) {
                    ItemScore subScore = (ItemScore) stateObj;
                    if (subScore != null) {
                        if (score.getScoreInfo().getSubScores() == null)
                            score.getScoreInfo().setSubScores(new ArrayList<ItemScoreInfo>());

                        // remove any bindings from the subscore that is not requested as an
                        // outgoing binding
                        if (responseInfo.getOutgoingBindings() == null) {
                            // No bindings requested - clear everything
                            // TODO: Recursively go through the subscore's children to clear
                            // those bindings also.
                            subScore.getScoreInfo().getRationale().getBindings().clear();
                        } else if (responseInfo.getOutgoingBindings().contains(VarBinding.ALL)) {
                            // All bindings requested
                            // don't remove anything. let them all through
                        } else {
                            // specific bindings requested. Remove anything not explicitly
                            // asked for.
                            // TODO: Recursively go through the subscore's children to filter
                            // them also
                            VarBinding[] subScoreBindings = subScore.getScoreInfo().getRationale().getBindings()
                                    .toArray(new VarBinding[subScore.getScoreInfo().getRationale().getBindings()
                                            .size()]);

                            for (int counter1 = 0; counter1 < subScoreBindings.length; ++counter1) {
                                final VarBinding subScoreBinding = subScoreBindings[counter1];
                                if (!CollectionUtils.exists(responseInfo.getOutgoingBindings(), new Predicate() {

                                    @Override
                                    public boolean evaluate(Object arg0) {
                                        return StringUtils.equals(((VarBinding) arg0).getName(),
                                                subScoreBinding.getName());
                                    }
                                })) {
                                    subScore.getScoreInfo().getRationale().getBindings().remove(counter1);
                                    --counter1;
                                }
                            }

                        }
                        score.getScoreInfo().getSubScores().add(subScore.getScoreInfo());
                    }

                }
                // note: ScoreLatency is miliseconds
                score.setScoreLatency(System.currentTimeMillis() - startTime);
                return score;

            } else {
                score.getScoreInfo().setStatus(ScoringStatus.ScoringError);
                score.getScoreInfo().getRationale().setMsg("Error changing score to int, score array values='"
                        + StringUtils.join(scoreArr, ',') + "'.");
                return score;
            }

        } catch (final Exception e) {
            e.printStackTrace();
            _logger.error("Error : " + e.getMessage());
            score.getScoreInfo().setPoints(-1);
            score.getScoreInfo().setStatus(ScoringStatus.ScoringError);
            score.getScoreInfo().getRationale().setMsg("Error processing rubric. Message: " + e.getMessage());
            score.getScoreInfo().getRationale().setException(e);
            return score;
        }
    }

    // / <summary>
    // / get an XmlReader object for the rubric
    // / </summary>
    // / <param name="ri"></param>
    // / <returns></returns>
    private XmlReader getReader(RubricContentType rubricContentType, Object rubricObject)
            throws JDOMException, IOException, XmlReaderException, URISyntaxException {
        try {
            if (rubricContentType == RubricContentType.Uri) {
                return XmlReader.create(new URI(rubricObject.toString()));
            } else {// rubric is a string
                return new XmlReader(new StringReader(rubricObject.toString()));
            }
        } catch (XmlReaderException xe) {
            String theStr = "<null>";
            if (rubricObject != null)
                theStr = rubricObject.toString();
            _logger.error(String.format("Exception: %s, input string: '%s'", xe.getMessage(), theStr), xe);
            throw xe;
        }
    }

}