org.sakaiproject.evaluation.tool.utils.RenderingUtils.java Source code

Java tutorial

Introduction

Here is the source code for org.sakaiproject.evaluation.tool.utils.RenderingUtils.java

Source

/**
 * Copyright 2005 Sakai Foundation Licensed under the
 * Educational Community License, Version 2.0 (the "License"); you may
 * not use this file except in compliance with the License. You may
 * obtain a copy of the License at
 *
 * http://www.osedu.org/licenses/ECL-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an "AS IS"
 * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing
 * permissions and limitations under the License.
 */
package org.sakaiproject.evaluation.tool.utils;

import java.text.DateFormat;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.sakaiproject.evaluation.constant.EvalConstants;
import org.sakaiproject.evaluation.logic.model.EvalGroup;
import org.sakaiproject.evaluation.logic.EvalAuthoringService;
import org.sakaiproject.evaluation.model.EvalEvaluation;
import org.sakaiproject.evaluation.model.EvalTemplateItem;
import org.sakaiproject.evaluation.tool.producers.EvaluationNotificationsProducer;
import org.sakaiproject.evaluation.tool.producers.EvaluationRespondersProducer;
import org.sakaiproject.evaluation.tool.producers.ReportChooseGroupsProducer;
import org.sakaiproject.evaluation.tool.producers.ReportsViewingProducer;
import org.sakaiproject.evaluation.tool.renderers.ItemRenderer;
import org.sakaiproject.evaluation.tool.viewparams.EvalViewParameters;
import org.sakaiproject.evaluation.tool.viewparams.ReportParameters;
import org.sakaiproject.evaluation.utils.EvalUtils;
import org.sakaiproject.evaluation.utils.TemplateItemDataList;
import org.sakaiproject.evaluation.utils.TemplateItemDataList.DataTemplateItem;
import org.sakaiproject.evaluation.utils.TemplateItemDataList.TemplateItemGroup;
import org.sakaiproject.evaluation.utils.TemplateItemUtils;

import uk.org.ponder.rsf.components.UIBranchContainer;
import uk.org.ponder.rsf.components.UIComponent;
import uk.org.ponder.rsf.components.UIInternalLink;
import uk.org.ponder.rsf.components.UIMessage;
import uk.org.ponder.rsf.components.UIOutput;
import uk.org.ponder.rsf.components.decorators.UITooltipDecorator;
import uk.org.ponder.rsf.viewstate.ViewParameters;

/**
 * A class to keep sharing rendering logic in
 * 
 * @author Aaron Zeckoski (aaron@caret.cam.ac.uk)
 */
public class RenderingUtils {

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

    private EvalAuthoringService authoringService;

    public void setAuthoringService(EvalAuthoringService authoringService) {
        this.authoringService = authoringService;
    }

    /**
     * Calculates the weighted average and number of counted answers from the responseArray
     * (this comes from the {@link TemplateItemDataList#getAnswerChoicesCounts(String, int, List)}) <br/>
     * http://en.wikipedia.org/wiki/Weighted_mean
     * 
     * @param responseArray an array of answers in the order such that 0 weighted answers are in the first array slot, etc.)
     * @return the AnswersMean object which holds the answers count and the mean
     */
    public static AnswersMean calculateMean(int[] responseArray) {
        if (responseArray == null) {
            throw new IllegalArgumentException("responseArray cannot be null");
        }
        int responseCount = responseArray.length - 1; // remove the NA count from the end
        int totalAnswers = 0;
        int totalValue = 0;
        //int totalWeight = 0;
        for (int i = 0; i < responseCount; i++) {
            int weight = i + 1;
            //totalWeight += weight;
            totalAnswers += responseArray[i];
            totalValue += (weight * responseArray[i]);
        }
        double weightedAverage = 0.0d;
        if (totalAnswers > 0) {
            weightedAverage = (double) totalValue / (double) totalAnswers; // (double)totalWeight;
        }
        return new AnswersMean(totalAnswers, weightedAverage);
    }

    public static AnswersMean calculateAnswersMean(int[] responseArray, List<String> answersArray, boolean usaNA) {
        /* 20140226 - daniel.merino@unavarra.es - https://jira.sakaiproject.org/browse/EVALSYS-1100
         * Calculate weighted mean of answers or of answer numbers depending if values are numeric.
         * In all cases, N/A value is excluded.
         * responseArray: 3,7,2,9,5,3 (votes of each answer)
         * answersArray: 2,4,6,8,10 || A,B,C,D,E || 1,2,3,4,5,N/A  || A,B,C,D,E,N/A
         */
        if (responseArray == null) {
            throw new IllegalArgumentException("responseArray cannot be null");
        }
        int responseCount = responseArray.length - 1; // remove the NA count from the end
        int totalAnswers = 0;
        int totalAnswersWithNA;
        int totalValue = 0;

        int[] realValues = new int[responseCount];
        boolean numerico = true; //If there is a non-numeric value, mean of indexes is made.

        //We take all answers. If N/A is used, all but the last one.
        for (int i = 0; i < responseCount; i++) {
            try {
                realValues[i] = new Integer(answersArray.get(i));
            } catch (Exception e) {
                numerico = false;
                break;
            }
        }

        for (int i = 0; i < responseCount; i++) {
            if (!numerico) {
                //Not numeric values. Mean of answers indexes.
                int weight = i + 1;
                totalAnswers += responseArray[i];
                totalValue += (weight * responseArray[i]);
            } else {
                //Numeric values. Mean of answers.
                totalAnswers += responseArray[i];
                totalValue += (realValues[i] * responseArray[i]);
            }
        }
        if (usaNA)
            totalAnswersWithNA = totalAnswers + responseArray[responseArray.length - 1];
        else
            totalAnswersWithNA = totalAnswers;

        double weightedAverage = 0.0d;
        if (totalAnswers > 0) {
            weightedAverage = (double) totalValue / (double) totalAnswers;
        }
        return new AnswersMean(totalAnswersWithNA, weightedAverage);
    }

    public static class AnswersMean {
        private static final DecimalFormat DF = new DecimalFormat("#0.00");

        public String meanText;

        /**
         * @return the weighted mean as text
         */
        public String getMeanText() {
            return meanText;
        }

        public double mean;

        /**
         * @return the weighted mean
         */
        public double getMean() {
            return mean;
        }

        public int answersCount;

        /**
         * @return the number of answered items (not counting NA)
         */
        public int getAnswersCount() {
            return answersCount;
        }

        AnswersMean(int answers, double mean) {
            this.answersCount = answers;
            this.mean = mean;
            this.meanText = DF.format(mean);
        }

    }

    /**
     * getMatrixLabels() creates a list of either 2 or 3 labels that
     * will be displayed above the Matrix rendered scale.  By definition,
     * no scales will have 0 or 1 entries; there will always be at least 2.
     * The third entry will only be included if there are 5 or more
     * entries.  
     * <p>If the list contains a 3rd element, the 3rd element will be the middle
     * label.  We always know that the 1st element is the beginning and the 
     * second element is the end.
     * <p>2 entries in returns 2 entries (beginning and end)
     * <br>3 entries in returns 2 entries (beginning and end)
     * <br>4 entries in returns 2 entries (beginning and end)
     * <br>5 entries or more returns 3 entries (beginning, end, and middle)
     * <p>For scales with 5 or more entries, the middle entry of the scale will
     * be returned.  For lists with an even number of elements, the element before
     * the middle will be returned (i.e. a 6 element scale will return 1st, 3rd, and 6th)
     * 
     * @param scaleOptions the array of scale options for a matrix templateItem
     * @return List (see method comment)
     */
    public static List<String> getMatrixLabels(List<String> scaleOptions) {
        List<String> list = new ArrayList<>();
        if (scaleOptions != null && scaleOptions.size() > 0) {
            list.add(scaleOptions.get(0));
            list.add(scaleOptions.get(scaleOptions.size() - 1));
            if (scaleOptions.size() > 4) {
                int middleIndex = (scaleOptions.size() - 1) / 2;
                list.add(scaleOptions.get(middleIndex));
            }
        }
        return list;
    }

    /**
     * Calculate the proper set of scale labels to use for a template item
     * in a report based on the item type (note, this will only return useful data for scale items)
     * 
     * @param templateItem any template item (should be fully populated)
     * @param scaleOptions the array of scale options for this templateItem
     * @return the array of scale labels (or null if this is not scaled/MC/MA/block child)
     */
    public static List<String> makeReportingScaleLabels(EvalTemplateItem templateItem, List<String> scaleOptions) {
        if (templateItem == null) {
            throw new IllegalArgumentException("templateItem must be set");
        }
        List<String> scaleLabels = new ArrayList<>();
        String itemType = TemplateItemUtils.getTemplateItemType(templateItem);
        if (EvalConstants.ITEM_TYPE_MULTIPLECHOICE.equals(itemType)
                || EvalConstants.ITEM_TYPE_MULTIPLEANSWER.equals(itemType)) {
            // default to scale options for MC and MA
            scaleLabels = scaleOptions;
        } else if (EvalConstants.ITEM_TYPE_SCALED.equals(itemType)
                || EvalConstants.ITEM_TYPE_BLOCK_CHILD.equals(itemType) // since BLOCK_CHILD is always a scaled item
        ) {
            // only do something here if this item type can handle a scale
            if (LOG.isDebugEnabled()) {
                LOG.debug(
                        "templateItem (" + templateItem.getId() + ") scaled item rendering check: " + templateItem);
            }
            if (scaleOptions == null || scaleOptions.isEmpty()) {
                // if scale options are missing then try to get them from the item
                // NOTE: this could throw a NPE - not much we can do about that if it happens
                scaleOptions = templateItem.getItem().getScale().getOptions();
            }
            scaleLabels = ((List) ((ArrayList) scaleOptions).clone()); // default to just using the options array (use a copy)
            String scaleDisplaySetting = templateItem.getScaleDisplaySetting();
            if (scaleDisplaySetting == null && templateItem.getItem() != null) {
                scaleDisplaySetting = templateItem.getItem().getScaleDisplaySetting();
            }
            if (scaleDisplaySetting == null) {
                // this should not happen but just in case it does, we want to trap and warn about it
                LOG.warn("templateItem (" + templateItem.getId()
                        + ") without a scale display setting, using defaults for rendering: " + templateItem);
            } else if (scaleDisplaySetting.equals(EvalConstants.ITEM_SCALE_DISPLAY_MATRIX)
                    || scaleDisplaySetting.equals(EvalConstants.ITEM_SCALE_DISPLAY_MATRIX_COLORED)) {
                if (LOG.isDebugEnabled()) {
                    LOG.debug("templateItem (" + templateItem.getId() + ") is a matrix type item: ");
                }
                /* MATRIX - special labels for the matrix items
                 * Show numbers in front (e.g. "blah" becomes "1 - blah")
                 * and only show text if the label was display in take evals (e.g. "1 - blah, 2, 3, 4 - blah, ...)
                 */
                List<String> matrixLabels = RenderingUtils.getMatrixLabels(scaleOptions);
                for (int i = 0; i < scaleLabels.size(); i++) {
                    String label = scaleLabels.get(i);
                    if (matrixLabels.contains(label)) {
                        scaleLabels.add(i, (i + 1) + " - " + scaleLabels.get(i));
                    } else {
                        scaleLabels.add(i, String.valueOf(i + 1));
                    }
                }
            }
        }
        return scaleLabels;
    }

    /**
     * This will produce the valid message key given a category constant
     * @param categoryConstant
     * @return the message key
     */
    public static String getCategoryLabelKey(String categoryConstant) {
        String categoryMessage = "unknown.caps";
        if (EvalConstants.ITEM_CATEGORY_COURSE.equals(categoryConstant)) {
            categoryMessage = "modifyitem.course.category";
        } else if (EvalConstants.ITEM_CATEGORY_INSTRUCTOR.equals(categoryConstant)) {
            categoryMessage = "modifyitem.instructor.category";
        } else if (EvalConstants.ITEM_CATEGORY_ASSISTANT.equals(categoryConstant)) {
            categoryMessage = "modifyitem.ta.category";
        } else if (EvalConstants.ITEM_CATEGORY_ENVIRONMENT.equals(categoryConstant)) {
            categoryMessage = "modifyitem.environment.category";
        }
        return categoryMessage;
    }

    /**
     * Renders the reports/results column content (since the logic is complex)
     * 
     * @param container the branch container (must contain the following elements):
     *      evalReportDisplay (output)
     *      evalReportDisplayLink (link)
     *      evalRespondentsDisplayLink (link)
     * @param eval the evaluation
     * @param group the eval group
     * @param viewDate the date at which results can be viewed
     * @param df the formatter for the dates
     * @param responsesNeeded responses needed before results can be viewed (0 indicates they can be viewed now),
     *      normally should be the output from EvalBeanUtils.getResponsesNeededToViewForResponseRate(responsesCount, enrollmentsCount)
     * @param responsesRequired the int value of EvalSettings.RESPONSES_REQUIRED_TO_VIEW_RESULTS
     * @param evalResultsViewable true if the reports can be viewed based on eval state, prefs, and dates, 
     *      usually the result of EvalBeanUtils.checkInstructorViewResultsForEval(),
     *      NOTE: this doesn't guarantee the link is visible as there might not be enough respondents 
     *      or the view date may not be reached yet (should handle EvalSettings.INSTRUCTOR_ALLOWED_VIEW_RESULTS)
     * 
     * Sample rendering html (from summary.html):
      <tr rsf:id="evalResponsesList:">
    ...
    <td nowrap="nowrap">
      <span rsf:id="evalReportDisplay"></span>
      <a rsf:id="evalReportDisplayLink" href="report_view.html">results</a>
      <a rsf:id="evalRespondentsDisplayLink" class="left-separator" href="evaluation_responders.html">respondents</a>
    </td>
      </tr>
     *
     */
    public static void renderResultsColumn(UIBranchContainer container, EvalEvaluation eval, EvalGroup group,
            Date viewDate, DateFormat df, int responsesNeeded, int responsesRequired, boolean evalResultsViewable) {

        Date now = new Date();

        if (container == null) {
            throw new IllegalArgumentException("container must be set");
        }
        if (eval == null) {
            throw new IllegalArgumentException("eval must be set");
        }
        String evalState = EvalUtils.getEvaluationState(eval, true);
        if (viewDate == null) {
            viewDate = eval.getSafeViewDate(); // ensure no NPE
        }
        if (df == null) {
            df = DateFormat.getDateInstance(DateFormat.MEDIUM);
        }
        String viewableDate = df.format(viewDate);
        /* Reports column logic: 
         * - if eval OPEN (in progress) 
         * -- if view date not reached and if responses count not reached: show "{viewDate}: if at least {num} responses" 
         * -- if responses count not reached: show "After {num} more responses" 
         * -- if view date not reached but responses count reached: show "{viewDate}" 
         * -- if INSTRUCTOR_ALLOWED_VIEW_RESULTS and responses count reached: show link to report view 
         * - if eval CLOSED 
         * -- if responses count not reached: show "After {num} more responses" 
         * -- if view date not reached but responses count reached: show "{viewDate}" 
         * -- if view date and responses count reached: show link to report view
         */
        boolean evalOpen = EvalUtils.checkStateBefore(evalState, EvalConstants.EVALUATION_STATE_CLOSED, false); // eval is open (still in progress)
        if (evalOpen && !evalResultsViewable && responsesNeeded > 0) {
            // show view date + responses message (only if the eval is still OPEN)
            // controlevaluations.eval.report.viewablew.awaiting.responses
            UIMessage resultOutput = UIMessage.make(container, "evalReportDisplay",
                    "controlevaluations.eval.report.viewable.least.responses",
                    new Object[] { viewableDate, responsesRequired });
            // indicate the viewable date as well
            resultOutput.decorate(new UITooltipDecorator(
                    UIMessage.make("controlevaluations.eval.report.viewable.on", new Object[] { viewableDate })));
        } else if (responsesNeeded > 0) {
            // not viewable yet because there are not enough responses
            UIMessage resultOutput = UIMessage.make(container, "evalReportDisplay",
                    "controlevaluations.eval.report.after.responses", new Object[] { responsesNeeded });
            // indicate the viewable date as well
            resultOutput.decorate(new UITooltipDecorator(
                    UIMessage.make("controlevaluations.eval.report.viewable.on", new Object[] { viewableDate })));
        } else if (now.before(viewDate) || !evalResultsViewable) {
            // not viewable yet because of the view date
            UIOutput resultOutput = UIOutput.make(container, "evalReportDisplay", viewableDate);
            if (responsesNeeded == 0) {
                // just show date if we have enough responses
                resultOutput.decorate(new UITooltipDecorator(UIMessage
                        .make("controlevaluations.eval.report.viewable.on", new Object[] { viewableDate })));
            } else {
                // show if responses are still needed
                resultOutput.decorate(new UITooltipDecorator(UIMessage.make(
                        "controlevaluations.eval.report.awaiting.responses", new Object[] { responsesNeeded })));
            }
        } else { // if (evalResultsViewable)
            // reports are viewable so just display the reports link
            ViewParameters viewparams;
            if (group != null) {
                viewparams = new ReportParameters(ReportsViewingProducer.VIEW_ID, eval.getId(),
                        new String[] { group.evalGroupId });
            } else {
                viewparams = new ReportParameters(ReportChooseGroupsProducer.VIEW_ID, eval.getId());
            }
            UIInternalLink.make(container, "evalReportDisplayLink",
                    UIMessage.make("controlevaluations.eval.report.link"), viewparams);
        }
    }

    /**
     * Renders the response rate column (since the logic is complex)
     * 
     * @param container the branch container (must contain the following elements):
     *      responseRateDisplay (output)
     *      responseRateLink (link)
     * @param evaluationId the id of the evaluation
     * @param responsesNeeded responses needed before results can be viewed (0 indicates they can be viewed now),
     *      normally should be the output from EvalBeanUtils.getResponsesNeededToViewForResponseRate(responsesCount, enrollmentsCount)
     * @param responseString the string representing the response rate output
     * @param allowedViewResponders if true, this user can view the responders listing,
     *      normally only if user have view responders permission or is admin user
     * @param allowedEmailStudents if true, this user can send emails to evaluators,
     *      normally only if EvalSettings.INSTRUCTOR_ALLOWED_EMAIL_STUDENTS is true or is admin/owner of eval
     * 
     * Sample html (from summary.html):
      <tr rsf:id="evalResponsesList:">
    ...
    <td nowrap="nowrap">
      <a rsf:id="responseRateLink" href="evaluation_responders.html">2 of 39</a>
      <span rsf:id="responseRateDisplay"></span>
    </td>
      </tr>
     * 
     */
    public static void renderReponseRateColumn(UIBranchContainer container, Long evaluationId, int responsesNeeded,
            String responseString, boolean allowedViewResponders, boolean allowedEmailStudents) {
        if (container == null) {
            throw new IllegalArgumentException("container must be set");
        }
        if (evaluationId == null) {
            throw new IllegalArgumentException("evaluationId must be set");
        }
        if (responseString == null || "".equals(responseString)) {
            throw new IllegalArgumentException("responseString must be set");
        }
        /* Responses column:
         * - if min responses reached and user have view responders permission: link to the responders view 
         * - else if INSTRUCTOR_ALLOWED_EMAIL_STUDENTS: link to notifications (send emails) view 
         * - else no options enabled and not admin ONLY show the text of the responses info (and tooltip if min responses not reached)
         */
        boolean showRespondersLink = (responsesNeeded == 0 && allowedViewResponders);
        UIComponent responseRateCompoenent;
        if (allowedEmailStudents || showRespondersLink) {
            ViewParameters viewparams;
            if (showRespondersLink) {
                viewparams = new EvalViewParameters(EvaluationRespondersProducer.VIEW_ID, evaluationId);
            } else if (allowedEmailStudents) {
                viewparams = new EvalViewParameters(EvaluationNotificationsProducer.VIEW_ID, evaluationId);
            } else {
                throw new RuntimeException(
                        "Bad logic in renderReponseRateColumn: should not be possible to reach this");
            }
            responseRateCompoenent = UIInternalLink.make(container, "responseRateLink",
                    UIMessage.make("controlevaluations.eval.responses.inline", new Object[] { responseString }),
                    viewparams);
        } else {
            responseRateCompoenent = UIMessage.make(container, "responseRateDisplay",
                    "controlevaluations.eval.responses.inline", new Object[] { responseString });
        }
        if (responsesNeeded > 0) {
            // show if responses are still needed
            responseRateCompoenent.decorate(new UITooltipDecorator(UIMessage
                    .make("controlevaluations.eval.report.awaiting.responses", new Object[] { responsesNeeded })));
        }
    }

    /**
     * Ensures all rendering calcs work the same way and generate the same general properties
     * 
     * @param dti the dti which holds the template item (wrap the template item if you need to)
     * @param eval (OPTIONAL) the evaluation related to the template item to be rendered
     * @param missingKeys (OPTIONAL) the set of missing keys which should cause the matching items to rendered with an invalid marker
     * @param renderProperties (OPTIONAL) the existing map of rendering properties (one is created if this is null)
     * @return the map of render properties (created if the input map is null)
     */
    public Map<String, Object> makeRenderProps(DataTemplateItem dti, EvalEvaluation eval, Set<String> missingKeys,
            Map<String, Object> renderProperties) {
        if (dti == null || dti.templateItem == null) {
            throw new IllegalArgumentException("dti and dti.templateItem cannot be null");
        }

        if (renderProperties == null) {
            renderProperties = new HashMap<>();
        }

        boolean evalRequiresItems = false;
        if (eval != null) {
            evalRequiresItems = !EvalUtils.safeBool(eval.getBlankResponsesAllowed(), true);
        }

        boolean isDtiCompulsory = authoringService.isCompulsory(dti.templateItem, eval);

        if (isDtiCompulsory || (evalRequiresItems && dti.isRequireable())) {
            renderProperties.put(ItemRenderer.EVAL_PROP_ANSWER_REQUIRED, Boolean.TRUE);
        } else {
            renderProperties.remove(ItemRenderer.EVAL_PROP_ANSWER_REQUIRED);
        }

        if (missingKeys != null && !missingKeys.isEmpty()) {
            if (missingKeys.contains(dti.getKey())) {
                renderProperties.put(ItemRenderer.EVAL_PROP_RENDER_INVALID, Boolean.TRUE);
            }
        } else {
            renderProperties.remove(ItemRenderer.EVAL_PROP_RENDER_INVALID);
        }

        // loop through and add in children render props
        if (dti.isBlockParent()) {
            List<DataTemplateItem> children = dti.getBlockChildren();
            for (DataTemplateItem childDTI : children) {
                HashMap<String, Object> childRenderProps = new HashMap<>();
                makeRenderProps(childDTI, eval, missingKeys, childRenderProps);
                String key = "child-" + childDTI.templateItem.getId();
                renderProperties.put(key, childRenderProps);
            }
        }

        return renderProperties;
    }

    // NOTE: caching stuff copied from EntityBus project

    public static enum Header {
        EXPIRES("Expires"), DATE("Date"), ETAG("ETag"), LAST_MODIFIED("Last-Modified"), CACHE_CONTROL(
                "Cache-Control");

        private String value;

        Header(String value) {
            this.value = value;
        }

        @Override
        public String toString() {
            return value;
        }
    };

    /**
     * Set the no-cache headers for this response
     * @param res the servlet response
     */
    public static void setNoCacheHeaders(HttpServletResponse res) {
        long currentTime = System.currentTimeMillis();
        res.setDateHeader(Header.DATE.toString(), currentTime);
        res.setDateHeader(Header.EXPIRES.toString(), currentTime + 1000);

        res.setHeader(Header.CACHE_CONTROL.toString(), "no-cache");
        res.addHeader(Header.CACHE_CONTROL.toString(), "no-store");
        res.addHeader(Header.CACHE_CONTROL.toString(), "max-age=0");
        res.addHeader(Header.CACHE_CONTROL.toString(), "must-revalidate");
        res.addHeader(Header.CACHE_CONTROL.toString(), "private");
        res.addHeader(Header.CACHE_CONTROL.toString(), "s-maxage=0");
    }

    /**
     * Get a list of categories (a.k.a. associateTypes) that have items in this template. Categories are listed in {@link EvalConstants#ITEM_CATEGORY_ORDER}
     * and are like {@link EvalConstants#ITEM_CATEGORY_INSTRUCTOR}. {@link EvalConstants#ITEM_CATEGORY_COURSE} is always part of the returned list
     * @param templateId
     * @return
     */
    public List<String> extractCategoriesInTemplate(long templateId) {
        List<String> categories = new ArrayList<>();
        //Fetch all templateItems to find out what categories we have
        List<EvalTemplateItem> templateItems = authoringService.getTemplateItemsForTemplate(templateId,
                new String[] {}, new String[] {}, new String[] {});
        // make the TI data structure
        Map<String, List<String>> assiciates = new HashMap<>();
        List<String> fakeInstructor = new ArrayList<>();
        fakeInstructor.add("fakeinstructor");
        List<String> fakeAssistant = new ArrayList<>();
        fakeAssistant.add("fakeAssistant");
        assiciates.put(EvalConstants.ITEM_CATEGORY_INSTRUCTOR, fakeInstructor);
        assiciates.put(EvalConstants.ITEM_CATEGORY_ASSISTANT, fakeAssistant);

        TemplateItemDataList tidl = new TemplateItemDataList(templateItems, null, assiciates, null);

        for (TemplateItemGroup tig : tidl.getTemplateItemGroups()) {
            // check which category we have
            if (EvalConstants.ITEM_CATEGORY_COURSE.equals(tig.associateType)) {
                categories.add(EvalConstants.ITEM_CATEGORY_COURSE);
            } else if (EvalConstants.ITEM_CATEGORY_INSTRUCTOR.equals(tig.associateType)) {
                categories.add(EvalConstants.ITEM_CATEGORY_INSTRUCTOR);
            } else if (EvalConstants.ITEM_CATEGORY_ASSISTANT.equals(tig.associateType)) {
                categories.add(EvalConstants.ITEM_CATEGORY_ASSISTANT);
            }
        }

        return categories;

    }

}