org.wise.vle.web.VLEGetSpecialExport.java Source code

Java tutorial

Introduction

Here is the source code for org.wise.vle.web.VLEGetSpecialExport.java

Source

/**
 * Copyright (c) 2008-2015 Regents of the University of California (Regents).
 * Created by WISE, Graduate School of Education, University of California, Berkeley.
 * 
 * This software is distributed under the GNU General Public License, v3,
 * or (at your option) any later version.
 * 
 * Permission is hereby granted, without written agreement and without license
 * or royalty fees, to use, copy, modify, and distribute this software and its
 * documentation for any purpose, provided that the above copyright notice and
 * the following two paragraphs appear in all copies of this software.
 * 
 * REGENTS SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE. THE SOFTWARE AND ACCOMPANYING DOCUMENTATION, IF ANY, PROVIDED
 * HEREUNDER IS PROVIDED "AS IS". REGENTS HAS NO OBLIGATION TO PROVIDE
 * MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 * 
 * IN NO EVENT SHALL REGENTS BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
 * SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS,
 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
 * REGENTS HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package org.wise.vle.web;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.Timestamp;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.Vector;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import org.wise.portal.dao.ObjectNotFoundException;
import org.wise.portal.domain.attendance.StudentAttendance;
import org.wise.portal.domain.module.impl.CurnitGetCurnitUrlVisitor;
import org.wise.portal.domain.project.Project;
import org.wise.portal.domain.project.ProjectMetadata;
import org.wise.portal.domain.run.Run;
import org.wise.portal.domain.user.User;
import org.wise.portal.presentation.web.controllers.ControllerUtil;
import org.wise.portal.presentation.web.controllers.run.RunUtil;
import org.wise.portal.service.attendance.StudentAttendanceService;
import org.wise.portal.service.offering.RunService;
import org.wise.portal.service.vle.VLEService;
import org.wise.portal.service.workgroup.WorkgroupService;
import org.wise.vle.domain.user.UserInfo;
import org.wise.vle.domain.work.StepWork;
import org.wise.vle.utils.FileManager;

@Controller
public class VLEGetSpecialExport {

    @Autowired
    private Properties wiseProperties;

    @Autowired
    private VLEService vleService;

    @Autowired
    private RunService runService;

    @Autowired
    private WorkgroupService workgroupService;

    @Autowired
    private StudentAttendanceService studentAttendanceService;

    //the max number of step work columns we need, only used for "allStudentWork"
    private int maxNumberOfStepWorkParts = 1;

    private HashMap<String, JSONObject> nodeIdToNodeContent = new HashMap<String, JSONObject>();

    private HashMap<String, JSONObject> nodeIdToNode = new HashMap<String, JSONObject>();

    private HashMap<String, String> nodeIdToNodeTitles = new HashMap<String, String>();

    private HashMap<String, String> nodeIdToNodeTitlesWithPosition = new HashMap<String, String>();

    private HashMap<Integer, String> workgroupIdToPeriodName = new HashMap<Integer, String>();

    private HashMap<Integer, String> workgroupIdToUserIds = new HashMap<Integer, String>();

    private HashMap<Long, JSONArray> workgroupIdToStudentAttendance = new HashMap<Long, JSONArray>();

    private HashMap<Integer, JSONArray> workgroupIdToWiseIds = new HashMap<Integer, JSONArray>();

    private List<String> nodeIdList = new Vector<String>();

    //the start time of the run (when the run was created)
    private String startTime = "N/A";

    //the end time of the run (when the run was archived)
    private String endTime = "N/A";

    //holds the teacher's username and workgroupid
    private JSONObject teacherUserInfoJSONObject;

    //run and project attributes
    private String runId = "";
    private String runName = "";
    private String projectId = "";
    private String parentProjectId = "";
    private String projectName = "";
    private String nodeId = "";

    //holds all the teacher workgroup ids
    private List<String> teacherWorkgroupIds = null;

    //the custom steps to export
    List<String> customSteps = null;

    //the number of columns to width auto size
    int numColumnsToAutoSize = 0;

    //the project meta data
    private JSONObject projectMetaData = null;

    private static long debugStartTime = 0;

    //the type of export "latestStudentWork" or "allStudentWork"
    private String exportType = "";

    private static Properties vleProperties = null;

    {
        try {
            //get the wise.properties file
            vleProperties = new Properties();
            vleProperties.load(getClass().getClassLoader().getResourceAsStream("wise.properties"));
        } catch (Exception e) {
            System.err.println("VLEGetSpecialExport could not read in vleProperties file");
            e.printStackTrace();
        }
    }

    /**
     * Clear the instance variables because only one instance of a servlet
     * is ever created
     */
    private void clearVariables() {
        //the max number of step work columns we need, only used for "allStudentWork"
        maxNumberOfStepWorkParts = 1;

        //mappings for the project and user
        nodeIdToNodeContent = new HashMap<String, JSONObject>();
        nodeIdToNode = new HashMap<String, JSONObject>();
        nodeIdToNodeTitles = new HashMap<String, String>();
        nodeIdToNodeTitlesWithPosition = new HashMap<String, String>();
        workgroupIdToPeriodName = new HashMap<Integer, String>();
        workgroupIdToUserIds = new HashMap<Integer, String>();
        workgroupIdToStudentAttendance = new HashMap<Long, JSONArray>();
        workgroupIdToWiseIds = new HashMap<Integer, JSONArray>();

        //the list of node ids in the project
        nodeIdList = new Vector<String>();

        //the start time of the run (when the run was created)
        startTime = "N/A";

        //the end time of the run (when the run was archived)
        endTime = "N/A";

        //holds the teacher's username and workgroupid
        teacherUserInfoJSONObject = null;

        //run and project attributes
        runId = "";
        runName = "";
        projectId = "";
        parentProjectId = "";
        projectName = "";
        nodeId = "";

        //holds all the teacher workgroup ids
        teacherWorkgroupIds = null;

        //holds the custom steps to export data for
        customSteps = new Vector<String>();

        //reset the number of columns to width auto size
        numColumnsToAutoSize = 0;

        //reset the project meta data
        projectMetaData = null;

        //holds the export type
        exportType = "";
    }

    /**
     * Compare two different millisecond times
     * @param time1 the earlier time (smaller)
     * @param time2 the later time (larger)
     * @return the difference between the times in seconds
     */
    private long getDifferenceInSeconds(long time1, long time2) {
        return (time2 - time1) / 1000;
    }

    /**
     * Display the difference between the current time and the
     * start time
     * @param label the label to display with the time difference
     */
    @SuppressWarnings("unused")
    private void displayCurrentTimeDifference(String label) {
        long currentTime = new Date().getTime();
        System.out.println(label + ": " + getDifferenceInSeconds(debugStartTime, currentTime));
    }

    /**
     * Generates and returns an excel xls of exported student data.
     */
    @RequestMapping("/specialExport")
    public ModelAndView doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        //get the signed in user
        User signedInUser = ControllerUtil.getSignedInUser();

        /*
         * clear the instance variables because only one instance of a servlet
         * is ever created
         */
        clearVariables();

        //obtain the start time for debugging purposes
        debugStartTime = new Date().getTime();

        //get the run and project attributes
        runId = request.getParameter("runId");
        runName = request.getParameter("runName");
        projectId = request.getParameter("projectId");
        projectName = request.getParameter("projectName");
        parentProjectId = request.getParameter("parentProjectId");
        nodeId = request.getParameter("nodeId");

        //the export type "latestStudentWork" or "allStudentWork"
        exportType = request.getParameter("exportType");

        Long runIdLong = null;
        try {
            runIdLong = new Long(runId);
        } catch (NumberFormatException e) {
            e.printStackTrace();
        }

        Run run = null;
        try {
            if (runId != null) {
                //get the run object
                run = runService.retrieveById(runIdLong);
            }
        } catch (ObjectNotFoundException e1) {
            e1.printStackTrace();
        }

        boolean allowedAccess = false;

        /*
         * admins can make a request
         * teachers that are owners of the run can make a request
         */
        if (SecurityUtils.isAdmin(signedInUser)) {
            //the user is an admin so we will allow this request
            allowedAccess = true;
        } else if (SecurityUtils.isTeacher(signedInUser)
                && SecurityUtils.isUserOwnerOfRun(signedInUser, runIdLong)) {
            //the user is a teacher that is an owner or shared owner of the run so we will allow this request
            allowedAccess = true;
        }

        if (!allowedAccess) {
            //user is not allowed to make this request
            response.sendError(HttpServletResponse.SC_FORBIDDEN);
            return null;
        }

        Project projectObj = run.getProject();
        ProjectMetadata metadata = projectObj.getMetadata();
        String projectMetaDataJSONString = metadata.toJSONString();

        String curriculumBaseDir = wiseProperties.getProperty("curriculum_base_dir");
        String rawProjectUrl = (String) run.getProject().getCurnit().accept(new CurnitGetCurnitUrlVisitor());
        String projectPath = curriculumBaseDir + rawProjectUrl;
        String projectFolderName = "";
        String projectFolderPath = "";

        if (rawProjectUrl != null) {
            // get the folder name e.g. 12345
            projectFolderName = rawProjectUrl.substring(1, rawProjectUrl.indexOf("/", 1));
        }

        if (projectPath != null) {
            // get the project folder path e.g. /user/wise/tomcat/webapps/curriculum/12345
            projectFolderPath = projectPath.substring(0, projectPath.lastIndexOf("/"));
        }

        //get the classmate user infos
        JSONArray classmateUserInfosJSONArray = RunUtil.getClassmateUserInfos(run, workgroupService, runService);

        //get the teacher info
        teacherUserInfoJSONObject = RunUtil.getTeacherUserInfo(run, workgroupService);

        //get the shared teacher infos
        JSONArray sharedTeacherUserInfosJSONArray = RunUtil.getSharedTeacherUserInfos(run, workgroupService);

        //get the run info
        JSONObject runInfoJSONObject = RunUtil.getRunInfo(run);

        //get all the student attendance entries for this run
        List<StudentAttendance> studentAttendanceList = studentAttendanceService
                .getStudentAttendanceByRunId(run.getId());
        JSONArray studentAttendanceJSONArray = new JSONArray();

        /*
         * loop through all the student attendance entries so we can
         * create JSONObjects out of them to put in our studentAttendanceJSONArray
         */
        for (int x = 0; x < studentAttendanceList.size(); x++) {
            //get a StudenAttendance object
            StudentAttendance studentAttendance = studentAttendanceList.get(x);

            //get the JSONObject representation
            JSONObject studentAttendanceJSONObj = studentAttendance.toJSONObject();

            //add it to our array
            studentAttendanceJSONArray.put(studentAttendanceJSONObj);
        }

        //get the path of the wise base dir
        String wiseBaseDir = vleProperties.getProperty("wiseBaseDir");

        try {
            //get the project meta data JSON object
            projectMetaData = new JSONObject(projectMetaDataJSONString);
        } catch (JSONException e2) {
            e2.printStackTrace();
        }

        try {
            if (runInfoJSONObject.has("startTime")) {
                //get the start time as a string
                String startTimeString = runInfoJSONObject.getString("startTime");

                if (startTimeString != null && !startTimeString.equals("null") && !startTimeString.equals("")) {
                    long startTimeLong = Long.parseLong(startTimeString);

                    Timestamp startTimeTimestamp = new Timestamp(startTimeLong);

                    //get the date the run was created
                    startTime = timestampToFormattedString(startTimeTimestamp);
                }
            }

            if (runInfoJSONObject.has("endTime")) {
                //get the end time as a string
                String endTimeString = runInfoJSONObject.getString("endTime");

                if (endTimeString != null && !endTimeString.equals("null") && !endTimeString.equals("")) {
                    long endTimeLong = Long.parseLong(endTimeString);

                    Timestamp endTimeTimestamp = new Timestamp(endTimeLong);

                    //get the date the run was archived
                    endTime = timestampToFormattedString(endTimeTimestamp);
                }
            }
        } catch (JSONException e1) {
            e1.printStackTrace();
        }

        //parse the student attendance data so we can query it later
        parseStudentAttendance(studentAttendanceJSONArray);

        //the List that will hold all the workgroup ids
        Vector<String> workgroupIds = new Vector<String>();

        JSONArray customStepsArray = new JSONArray();

        //gather the custom steps if the teacher is requesting a custom export
        if (exportType.equals("customLatestStudentWork") || exportType.equals("customAllStudentWork")) {
            String customStepsArrayJSONString = request.getParameter("customStepsArray");

            try {
                customStepsArray = new JSONArray(customStepsArrayJSONString);

                //loop through all the node ids
                for (int x = 0; x < customStepsArray.length(); x++) {
                    //add the node id to our list of custom steps
                    String nodeId = customStepsArray.getString(x);
                    customSteps.add(nodeId);
                }
            } catch (JSONException e) {
                e.printStackTrace();
            }
        }

        //create a file handle to the project file
        File projectFile = new File(projectPath);

        //the hash map to store workgroup id to period id
        HashMap<Integer, Integer> workgroupIdToPeriodId = new HashMap<Integer, Integer>();

        String teacherWorkgroupId = "";

        //create an array to hold all the teacher workgroup ids
        teacherWorkgroupIds = new ArrayList<String>();

        JSONObject project = null;

        try {
            //get the project JSON object
            project = new JSONObject(FileManager.getFileText(projectFile));

            //create the map of node ids to node titles
            makeNodeIdToNodeTitleAndNodeMap(project);

            /*
             * create the list of node ids in the order they appear in the project.
             * this also creates the map of node ides to node titles with positions
             */
            makeNodeIdList(project);

            //get the nodes
            JSONArray nodes = project.getJSONArray("nodes");

            //loop through all the nodes
            for (int x = 0; x < nodes.length(); x++) {
                //get a node
                JSONObject node = nodes.getJSONObject(x);

                try {
                    //get the text from the file
                    String fileText = FileManager
                            .getFileText(new File(projectFile.getParentFile(), node.getString("ref")));

                    //get the content for the node
                    JSONObject nodeContent = new JSONObject(fileText);

                    //put an entry into the hashmap with key as node id and value as JSON node content
                    nodeIdToNodeContent.put(node.getString("identifier"), nodeContent);
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (JSONException e) {
                    e.printStackTrace();
                }
            }

            //get the array of classmates
            //JSONArray classmateUserInfosJSONArray = new JSONArray(classmateUserInfos);

            //get the teacher user info
            //teacherUserInfoJSONObject = new JSONObject(teacherUserInfo);

            //get the owner teacher workgroup id
            teacherWorkgroupId = teacherUserInfoJSONObject.getString("workgroupId");

            //add the owner teacher
            teacherWorkgroupIds.add(teacherWorkgroupId);

            //get the shared teacher user infos
            //JSONArray sharedTeacherUserInfosJSONArray = new JSONArray(sharedTeacherUserInfos);

            //loop through all the shared teacher user infos
            for (int z = 0; z < sharedTeacherUserInfosJSONArray.length(); z++) {
                //get a shared teacher
                JSONObject sharedTeacherJSONObject = (JSONObject) sharedTeacherUserInfosJSONArray.get(z);

                if (sharedTeacherJSONObject != null) {
                    if (sharedTeacherJSONObject.has("workgroupId")) {
                        //get the shared teacher workgroup id
                        String sharedTeacherWorkgroupId = sharedTeacherJSONObject.getString("workgroupId");

                        //add the shared teacher workgroup id to the array
                        teacherWorkgroupIds.add(sharedTeacherWorkgroupId);
                    }
                }
            }

            //loop through all the classmates
            for (int y = 0; y < classmateUserInfosJSONArray.length(); y++) {
                //get a classmate
                JSONObject classmate = classmateUserInfosJSONArray.getJSONObject(y);

                //make sure workgroupId and periodId exist and are not null
                if (classmate.has("workgroupId") && !classmate.isNull("workgroupId")) {
                    //get the workgroup id for the classmate
                    int workgroupId = classmate.getInt("workgroupId");

                    if (classmate.has("periodId") && !classmate.isNull("periodId")) {
                        //get the period id for the classmate
                        int periodId = classmate.getInt("periodId");

                        //put an entry into the hashmap with key as workgroup id and value as period id
                        workgroupIdToPeriodId.put(workgroupId, periodId);
                    }

                    if (classmate.has("periodName") && !classmate.isNull("periodName")) {
                        //get the period name such as 1, 2, 3, or 4, etc.
                        String periodName = classmate.getString("periodName");
                        workgroupIdToPeriodName.put(workgroupId, periodName);
                    }

                    if (classmate.has("userIds") && !classmate.isNull("userIds")) {
                        /*
                         * get the student logins, this is a single string with the logins
                         * separated by ':'
                         */
                        String userIds = classmate.getString("userIds");
                        workgroupIdToUserIds.put(workgroupId, userIds);
                    }

                    if (classmate.has("periodId") && !classmate.isNull("periodId") && classmate.has("periodName")
                            && !classmate.isNull("periodName") && classmate.has("userIds")
                            && !classmate.isNull("userIds")) {

                        //add the workgroup id string to the List of workgroup ids
                        workgroupIds.add(workgroupId + "");
                    }

                    if (classmate.has("wiseIds") && !classmate.isNull("wiseIds")) {
                        //get the wise ids for this workgroup
                        JSONArray wiseIds = classmate.getJSONArray("wiseIds");
                        workgroupIdToWiseIds.put(workgroupId, wiseIds);
                    }
                }
            }
        } catch (JSONException e) {
            e.printStackTrace();
        }

        if (exportType.equals("specialExport")) {

            String nodeTitleWithPosition = "Step " + nodeIdToNodeTitlesWithPosition.get(nodeId);
            String fileName = runName + "-" + runId + "-" + nodeTitleWithPosition;
            fileName = fileName.replaceAll(" ", "_");

            /*
             * we will return a zipped folder that contains all the necessary
             * files to view the student work
             */
            response.setContentType("application/zip");
            response.addHeader("Content-Disposition", "attachment;filename=\"" + fileName + ".zip" + "\"");

            //create a folder that will contain all the files and will then be zipped
            File zipFolder = new File(fileName);
            zipFolder.mkdir();

            //create the file that will contain all the data in a JSON object
            File dataFile = new File(zipFolder, "data.js");

            JSONObject data = new JSONObject();
            JSONArray students = new JSONArray();

            try {
                //add the project, run, and step information
                data.put("projectName", projectName);
                data.put("projectId", projectId);
                data.put("runName", runName);
                data.put("runId", runId);
                data.put("stepName", nodeTitleWithPosition);
                data.put("nodeId", nodeId);
                data.put("projectFolderName", projectFolderName);
            } catch (JSONException e1) {
                e1.printStackTrace();
            }

            String nodeType = "";

            //get the step content
            JSONObject nodeContent = nodeIdToNodeContent.get(nodeId);

            if (nodeContent != null) {
                try {
                    //get the node type e.g. SVGDraw or mysystem2
                    nodeType = nodeContent.getString("type");

                    if (nodeType != null) {
                        //make the node type lower case for easy comparison later
                        nodeType = nodeType.toLowerCase();
                    }
                } catch (JSONException e) {
                    e.printStackTrace();
                }
            }

            if (nodeType == null) {

            } else if (nodeType.equals("svgdraw")) {
                if (wiseBaseDir != null && wiseBaseDir != "") {
                    //get the lz77.js file from the server
                    File sourcelz77File = new File(wiseBaseDir + "/vle/node/draw/svg-edit/lz77.js");

                    //create a lz77.js file in the folder we are creating
                    File newlz77File = new File(zipFolder, "lz77.js");

                    //copy the contents of the lz77.js file into our new file
                    FileUtils.copyFile(sourcelz77File, newlz77File);

                    //get the viewStudentWork.html file for svgdraw
                    File sourceViewStudentWorkFile = new File(wiseBaseDir + "/vle/node/draw/viewStudentWork.html");

                    //create a viewStudentWork.html file in the folder we are creating
                    File newViewStudentWorkFile = new File(zipFolder, "viewStudentWork.html");

                    //copy the contents of the viewStudentWork.html file into our new file
                    FileUtils.copyFile(sourceViewStudentWorkFile, newViewStudentWorkFile);

                    // get the project assets folder
                    File assetsFolder = new File(projectFolderPath, "assets");

                    // copy the assets folder into the folder we are creating
                    FileUtils.copyDirectoryToDirectory(assetsFolder, zipFolder);
                }
            } else if (nodeType.equals("mysystem2")) {
                if (wiseBaseDir != null && wiseBaseDir != "") {
                    MySystemExporter myExporter = new MySystemExporter(wiseBaseDir, zipFolder);
                    myExporter.copyFiles();
                }
            } else if (nodeType.equals("sensor")) {
                if (wiseBaseDir != null && wiseBaseDir != "") {
                    //get the paths of all the files we need to copy
                    Vector<String> filesToCopy = new Vector<String>();
                    filesToCopy.add(wiseBaseDir + "/vle/model/content.js");
                    filesToCopy.add(wiseBaseDir + "/vle/util/helperfunctions.js");
                    filesToCopy.add(wiseBaseDir + "/vle/lib/jquery/js/flot/jquery.flot.js");
                    filesToCopy.add(wiseBaseDir + "/vle/lib/jquery/js/flot/jquery.js");
                    filesToCopy.add(wiseBaseDir + "/vle/node/Node.js");
                    filesToCopy.add(wiseBaseDir + "/vle/model/nodevisit.js");
                    filesToCopy.add(wiseBaseDir + "/vle/node/sensor/sensor.js");
                    filesToCopy.add(wiseBaseDir + "/vle/node/sensor/SensorNode.js");
                    filesToCopy.add(wiseBaseDir + "/vle/node/sensor/sensorstate.js");
                    filesToCopy.add(wiseBaseDir + "/vle/node/sensor/viewStudentWork.html");

                    //loop through all the file paths
                    for (int x = 0; x < filesToCopy.size(); x++) {
                        //get a file path
                        String filePath = filesToCopy.get(x);

                        //copy the file to our zip folder
                        copyFileToFolder(filePath, zipFolder);
                    }

                    //get the original step content file
                    File originalStepContentFile = new File(projectFolderPath + "/" + nodeId);

                    //get the content as a string
                    String stepContentString = FileUtils.readFileToString(originalStepContentFile);

                    //make a variable definition for the step content object so we can access it as a variable
                    stepContentString = "var stepContent = " + stepContentString + ";";

                    //create the new file
                    File newStepContentFile = new File(zipFolder, "stepContent.js");

                    //write the step content to the stepContent.js file
                    FileUtils.writeStringToFile(newStepContentFile, stepContentString);
                }
            }

            //loop through the workgroup ids
            for (int x = 0; x < workgroupIds.size(); x++) {
                //get a workgroup id
                String userId = workgroupIds.get(x);
                Long userIdLong = Long.parseLong(userId);

                //get the UserInfo object for the workgroup id
                UserInfo userInfo = vleService.getUserInfoByWorkgroupId(userIdLong);

                //get the wise ids
                JSONArray wiseIds = workgroupIdToWiseIds.get(Integer.parseInt(userId));

                //get all the work for a workgroup id
                List<StepWork> stepWorksForWorkgroupId = vleService.getStepWorksByUserInfo(userInfo);

                //get all the step works for this node id
                List<StepWork> stepWorksForNodeId = getStepWorksForNodeId(stepWorksForWorkgroupId, nodeId);

                JSONArray studentDataArray = new JSONArray();

                //loop through all the step works
                for (int y = 0; y < stepWorksForNodeId.size(); y++) {
                    //get a stepwork
                    StepWork stepWork = stepWorksForNodeId.get(y);

                    //get the student data from the step work
                    JSONObject studentData = getStudentData(stepWork);

                    //put the student data into the student data array
                    studentDataArray.put(studentData);
                }

                JSONObject studentObject = new JSONObject();
                try {
                    //put the workgroup id into the student object
                    studentObject.put("workgroupId", userIdLong);

                    //put the wise ids into the student object
                    studentObject.put("wiseIds", wiseIds);

                    //put the array of student work into the student object
                    studentObject.put("studentDataArray", studentDataArray);
                } catch (JSONException e) {
                    e.printStackTrace();
                }

                //add the student object into the array
                students.put(studentObject);
            }

            try {
                //put the student array into the data object
                data.put("students", students);
            } catch (JSONException e) {
                e.printStackTrace();
            }

            /*
             * turn the student data object into a javascript declaration
             * e.g.
             * from
             * {}
             * to
             * var data = {};
             */
            String javascriptDataString = getJavascriptText("data", data);

            //write the data to the data.js file
            FileUtils.writeStringToFile(dataFile, javascriptDataString);

            //get the response output stream
            ServletOutputStream outputStream = response.getOutputStream();

            //create ZipOutputStream object
            ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(outputStream));

            //get path prefix so that the zip file does not contain the whole path
            // eg. if folder to be zipped is /home/lalit/test
            // the zip file when opened will have test folder and not home/lalit/test folder
            int len = zipFolder.getAbsolutePath().lastIndexOf(File.separator);
            String baseName = zipFolder.getAbsolutePath().substring(0, len + 1);

            //add the folder to the zip file
            addFolderToZip(zipFolder, out, baseName);

            //close the zip output stream
            out.close();

            //delete the folder that has been created on the server
            FileUtils.deleteDirectory(zipFolder);
        }

        //perform cleanup
        clearVariables();

        return null;
    }

    /**
     * Turn the JSONObject into a javascript object declaration in
     * the form of a string.
     * e.g.
     * before
     * {}
     * after
     * var data = {};
     * @param jsVariableName the javascript variable name that we will use
     * @param jsonObject a JSONObject
     * @return a string containing the javascript object declaration
     */
    private String getJavascriptText(String jsVariableName, JSONObject jsonObject) {
        StringBuffer result = new StringBuffer();

        if (jsonObject != null) {
            String jsonArrayString = "";
            try {
                /*
                 * get the string representation of the JSONObject with
                 * proper indentation (using 3 spaces per tab) so it 
                 * will be easy for a human to read 
                 */
                jsonArrayString = jsonObject.toString(3);
            } catch (JSONException e) {
                e.printStackTrace();
            }

            if (jsonArrayString != null && !jsonArrayString.equals("")) {
                //make the object declaration string
                result.append("var " + jsVariableName + " = ");
                result.append(jsonArrayString);
                result.append(";");
            }
        }

        return result.toString();
    }

    /**
     * Get the student data from the step work
     * @param stepWork the step work object
     * @return a string containing the student data. the
     * contents of the string depend on the step type
     * that the work was for.
     */
    private JSONObject getStudentData(StepWork stepWork) {
        JSONObject studentData = new JSONObject();

        //get the step work id
        Long stepWorkId = stepWork.getId();

        // get the server post time
        Timestamp postTime = stepWork.getPostTime();

        try {
            //set the step work id
            studentData.put("stepWorkId", stepWorkId);

            //set the default value in case there is no data
            studentData.put("data", JSONObject.NULL);

            //get the step work data
            String data = stepWork.getData();

            if (data != null && !data.equals("")) {
                //get the JSONObject of the data
                JSONObject jsonData = new JSONObject(data);

                if (postTime != null) {
                    // put the server post time into the JSON data
                    jsonData.put("postTime", postTime.getTime());
                }

                //put the data into the object we will return
                studentData.put("data", jsonData);
            }
        } catch (JSONException e1) {
            e1.printStackTrace();
        }

        return studentData;
    }

    /**
     * Get the step works only for a specific node id
     * @param stepWorks a list of StepWork objects
     * @param nodeId the node id we want student work for
     * @return a list of StepWork objects that are filtered
     * for a node id
     */
    private List<StepWork> getStepWorksForNodeId(List<StepWork> stepWorks, String nodeId) {
        //the list of StepWorks that will contain the StepWorks we want
        List<StepWork> filteredStepWorks = new Vector<StepWork>();

        //iterator for the list of StepWorks we will filter
        Iterator<StepWork> stepWorksIterator = stepWorks.iterator();

        //loop through all the StepWorks
        while (stepWorksIterator.hasNext()) {
            //get a StepWork
            StepWork stepWork = stepWorksIterator.next();

            //get the node id for the StepWork
            String stepWorkNodeId = stepWork.getNode().getNodeId();

            //see if the node id matches the node id we are looking for
            if (stepWorkNodeId != null && stepWorkNodeId.equals(nodeId)) {
                /*
                 * add the StepWork to our list of StepWorks that have the
                 * node id we want
                 */
                filteredStepWorks.add(stepWork);
            }
        }

        //return the list of StepWorks that are for the node id we want
        return filteredStepWorks;
    }

    private static void addFolderToZip(File folder, ZipOutputStream zip, String baseName) throws IOException {
        File[] files = folder.listFiles();
        for (File file : files) {
            if (file.isDirectory()) {
                // add folder to zip
                String name = file.getAbsolutePath().substring(baseName.length());
                ZipEntry zipEntry = new ZipEntry(name + "/");
                zip.putNextEntry(zipEntry);
                zip.closeEntry();
                addFolderToZip(file, zip, baseName);
            } else {
                // it's a file.            
                String name = file.getAbsolutePath().substring(baseName.length());
                ZipEntry zipEntry = new ZipEntry(name);
                zip.putNextEntry(zipEntry);
                IOUtils.copy(new FileInputStream(file), zip);
                zip.closeEntry();
            }
        }
    }

    /**
     * Get the timestamp as a string
     * @param timestamp the timestamp object
     * @return the timstamp as a string
     * e.g.
     * Mar 9, 2011 8:50:47 PM
     */
    private String timestampToFormattedString(Timestamp timestamp) {
        String timestampString = "";

        if (timestamp != null) {
            //get the object to format timestamps
            DateFormat dateTimeInstance = DateFormat.getDateTimeInstance();

            //get the timestamp for the annotation
            long time = timestamp.getTime();
            Date timestampDate = new Date(time);
            timestampString = dateTimeInstance.format(timestampDate);
        }

        return timestampString;
    }

    /**
     * Parse the student attendance data and put it into the workgroupIdToStudentAttendance HashMap
     * @param studentAttendanceArray a JSONArray containing all the student attendance rows
     */
    private void parseStudentAttendance(JSONArray studentAttendanceArray) {
        //loop through all the stuent attendance rows
        for (int x = 0; x < studentAttendanceArray.length(); x++) {
            try {
                //get a student attendence row
                JSONObject studentAttendanceEntry = studentAttendanceArray.getJSONObject(x);

                //get the workgroup id
                long workgroupId = studentAttendanceEntry.getLong("workgroupId");

                //get the JSONArray that holds all the student attendence entries for this workgroup id
                JSONArray workgroupIdStudentAttendance = workgroupIdToStudentAttendance.get(workgroupId);

                if (workgroupIdStudentAttendance == null) {
                    //the JSONArray does not exist so we will create it
                    workgroupIdStudentAttendance = new JSONArray();
                    workgroupIdToStudentAttendance.put(workgroupId, workgroupIdStudentAttendance);
                }

                //add the student attendence entry to the JSONArray
                workgroupIdStudentAttendance.put(studentAttendanceEntry);
            } catch (JSONException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * Create a map of node id to node titles by looping through the array
     * of nodes in the project file and creating an entry in the map
     * for each node
     * @param project the project JSON object
     * @return a map of node id to node titles
     */
    private void makeNodeIdToNodeTitleAndNodeMap(JSONObject project) {
        nodeIdToNodeTitles = new HashMap<String, String>();
        nodeIdToNode = new HashMap<String, JSONObject>();

        try {
            //get the array of nodes defined in the project
            JSONArray nodesJSONArray = project.getJSONArray("nodes");

            //loop through all the nodes
            for (int x = 0; x < nodesJSONArray.length(); x++) {
                //get a node
                JSONObject node = nodesJSONArray.getJSONObject(x);

                if (node != null) {
                    //obtain the id and title
                    String nodeId = node.getString("identifier");
                    String title = node.getString("title");

                    if (nodeId != null && title != null) {
                        //put the id and title into the map
                        nodeIdToNodeTitles.put(nodeId, title);
                    }

                    if (nodeId != null) {
                        nodeIdToNode.put(nodeId, node);
                    }
                }
            }
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

    /**
     * Make the list of node ids
     * 
     * Note: makeNodeIdToNodeTitlesMap() must be called before this function
     * 
     * @param project the project JSON object
     */
    private void makeNodeIdList(JSONObject project) {
        //make a new Vector and set it to the global list object
        nodeIdList = new Vector<String>();

        try {
            //get the sequences
            JSONArray sequences = project.getJSONArray("sequences");

            //get the start point of the project
            String startPoint = project.getString("startPoint");

            //pass startsequence to recursive function that traverses activities and steps
            traverseNodeIdsToMakeNodeIdList(sequences, startPoint, "", 1, startPoint);
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

    /**
     * Traverses the sequences in the project file to create a list of nodes in 
     * the order that they appear in the project and at the same time determining
     * the position of each node (e.g. 1.1, 1.2, 2.1, 2.2, etc.)
     * 
     * @param sequences the JSONArray of sequences
     * @param identifier the id of the sequence or node we are currently on
     * @param positionSoFar the position we have traversed down to so far
     * e.g. if we are on Activity 2
     * positionSoFar will be "2."
     * @param nodePosition the position within the current sequence
     * e.g. if we are on Activity 2, Step 3
     * @param startPoint the id of the start point sequence of the project
     * nodePosition will be 3
     */
    private void traverseNodeIdsToMakeNodeIdList(JSONArray sequences, String identifier, String positionSoFar,
            int nodePosition, String startPoint) {
        try {
            //try to get the project sequence with the given identifier
            JSONObject projectSequence = getProjectSequence(sequences, identifier);

            if (projectSequence == null) {
                //the identifier actually points to a node, this is our base case

                //whether to include the data for this step in the export
                boolean exportStep = true;

                if (customSteps.size() != 0) {
                    //the teacher has provided a list of custom steps

                    if (!customSteps.contains(identifier)) {
                        //the current node id is not listed in the custom steps so we will not export the data for it
                        exportStep = false;
                    }
                }

                if (exportStep) {
                    //we will export the data for this step

                    //add the identifier to our list of nodes
                    nodeIdList.add(identifier);

                    //obtain the title of the node
                    String nodeTitle = nodeIdToNodeTitles.get(identifier);

                    //add the pre-pend the position to the title
                    String nodeTitleWithPosition = positionSoFar + nodePosition + " " + nodeTitle;

                    //add the title with position to the map
                    nodeIdToNodeTitlesWithPosition.put(identifier, nodeTitleWithPosition);
                }
            } else {
                //the identifier points to a sequence so we need to loop through its refs
                JSONArray refs = projectSequence.getJSONArray("refs");

                if (!identifier.equals(startPoint)) {
                    /*
                     * only do this for sequences that are not the startsequence otherwise
                     * all the positions would start with "1."
                     * so instead of Activity 2, Step 5 being 1.2.5 we really just want 2.5
                     */
                    positionSoFar = positionSoFar + nodePosition + ".";
                }

                //loop through all the refs
                for (int x = 0; x < refs.length(); x++) {
                    //get the identifier for a ref
                    String refIdentifier = refs.getString(x);

                    //recursively call the traverse function on the refs
                    traverseNodeIdsToMakeNodeIdList(sequences, refIdentifier, positionSoFar, x + 1, startPoint);
                }
            }
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

    /**
     * Retrieves the JSONObject for a sequence with the given sequenceId
     * @param sequences a JSONArray of sequence JSONObjects
     * @param sequenceId the identifier of the sequence we want
     * @return the sequence JSONObject or null if we did not find it
     */
    private JSONObject getProjectSequence(JSONArray sequences, String sequenceId) {
        //loop through all the sequences
        for (int x = 0; x < sequences.length(); x++) {
            try {
                //get a sequence
                JSONObject sequence = sequences.getJSONObject(x);

                if (sequence != null) {
                    //check if the identifier of the sequence is the one we want
                    if (sequence.getString("identifier").equals(sequenceId)) {
                        //return the sequence since we have found it
                        return sequence;
                    }
                }
            } catch (JSONException e) {
                e.printStackTrace();
            }
        }

        //we did not find the sequence we wanted
        return null;
    }

    /**
     * Copy a file to a folder
     * @param sourcePath the absolute path to the file on the system
     * @param folder the folder object to copy to
     */
    private void copyFileToFolder(String sourcePath, File folder) {
        /*
         * get the file name
         * if the path is /Users/geoffreykwan/dev/apache-tomcat-7.0.28/webapps/wise/vle/lib/jquery/js/flot/jquery.js
         * the file name will be
         * jquery.js
         */
        String fileName = sourcePath.substring(sourcePath.lastIndexOf("/"));

        //get the source file
        File sourceFile = new File(sourcePath);

        //create the destination file
        File destinationFile = new File(folder, fileName);

        try {
            //copy the file
            FileUtils.copyFile(sourceFile, destinationFile);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}