de.lemo.dms.processing.questions.QFrequentPathsViger.java Source code

Java tutorial

Introduction

Here is the source code for de.lemo.dms.processing.questions.QFrequentPathsViger.java

Source

/**
 * File ./src/main/java/de/lemo/dms/processing/questions/QFrequentPathsViger.java
 * Lemo-Data-Management-Server for learning analytics.
 * Copyright (C) 2013
 * Leonard Kappe, Andreas Pursian, Sebastian Schwarzrock, Boris Wenzlaff
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
**/

/**
 * File ./main/java/de/lemo/dms/processing/questions/QFrequentPathsViger.java
 * Date 2013-01-24
 * Project Lemo Learning Analytics
 */

package de.lemo.dms.processing.questions;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.ws.rs.FormParam;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.criterion.Restrictions;

import ca.pfv.spmf.sequentialpatterns.AlgoFournierViger08;
import ca.pfv.spmf.sequentialpatterns.SequenceDatabase;
import ca.pfv.spmf.sequentialpatterns.Sequences;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import de.lemo.dms.core.Clock;
import de.lemo.dms.core.config.ServerConfiguration;
import de.lemo.dms.db.IDBHandler;
import de.lemo.dms.db.mapping.abstractions.ILogMining;
import de.lemo.dms.processing.MetaParam;
import de.lemo.dms.processing.Question;
import de.lemo.dms.processing.StudentHelper;
import de.lemo.dms.processing.resulttype.ResultListUserPathGraph;
import de.lemo.dms.processing.resulttype.UserPathLink;
import de.lemo.dms.processing.resulttype.UserPathNode;
import de.lemo.dms.processing.resulttype.UserPathObject;

/**
 * Read ther path data from the database and using the Bide algorithm to generates the frequent paths
 * without interruptions
 * 
 * @author Sebastian Schwarzrock
 */
@Path("frequentPathsViger")
public class QFrequentPathsViger extends Question {

    private Map<String, ILogMining> idToLogM = new HashMap<String, ILogMining>();
    private Map<String, List<Long>> requests = new HashMap<String, List<Long>>();
    private Map<String, Integer> idToInternalId = new HashMap<String, Integer>();
    private Map<Integer, String> internalIdToId = new HashMap<Integer, String>();

    @POST
    public ResultListUserPathGraph compute(@FormParam(MetaParam.COURSE_IDS) final List<Long> courses,
            @FormParam(MetaParam.USER_IDS) final List<Long> users,
            @FormParam(MetaParam.TYPES) final List<String> types,
            @FormParam(MetaParam.MIN_LENGTH) final Long minLength,
            @FormParam(MetaParam.MAX_LENGTH) final Long maxLength,
            @FormParam(MetaParam.MIN_SUP) final Double minSup,
            @FormParam(MetaParam.SESSION_WISE) final boolean sessionWise,
            @FormParam(MetaParam.START_TIME) final Long startTime,
            @FormParam(MetaParam.END_TIME) final Long endTime,
            @FormParam(MetaParam.GENDER) final List<Long> gender) {

        validateTimestamps(startTime, endTime);

        final ArrayList<UserPathNode> nodes = Lists.newArrayList();
        final ArrayList<UserPathLink> links = Lists.newArrayList();

        if (logger.isDebugEnabled()) {
            if ((courses != null) && (courses.size() > 0)) {
                StringBuffer buffer = new StringBuffer();
                buffer.append("Parameter list: Courses: " + courses.get(0));
                for (int i = 1; i < courses.size(); i++) {
                    buffer.append(", " + courses.get(i));
                }
                logger.debug(buffer.toString());
            }
            if ((users != null) && (users.size() > 0)) {
                StringBuffer buffer = new StringBuffer();
                buffer.append("Parameter list: Users: " + users.get(0));
                for (int i = 1; i < users.size(); i++) {
                    buffer.append(", " + users.get(i));
                }
                logger.debug(buffer.toString());
            }
            if ((types != null) && (types.size() > 0)) {
                StringBuffer buffer = new StringBuffer();
                buffer.append("Parameter list: Types: : " + types.get(0));
                for (int i = 1; i < types.size(); i++) {
                    buffer.append(", " + types.get(i));
                }
                logger.debug(buffer.toString());
            }
            if ((minLength != null) && (maxLength != null) && (minLength < maxLength)) {
                logger.debug("Parameter list: Minimum path length: : " + minLength);
                logger.debug("Parameter list: Maximum path length: : " + maxLength);
            }
            logger.debug("Parameter list: Minimum Support: : " + minSup);
            logger.debug("Parameter list: Session Wise: : " + sessionWise);
            logger.debug("Parameter list: Start time: : " + startTime);
            logger.debug("Parameter list: End time: : " + endTime);
        }

        final IDBHandler dbHandler = ServerConfiguration.getInstance().getMiningDbHandler();

        final Session session = dbHandler.getMiningSession();

        try {
            final SequenceDatabase sequenceDatabase = new SequenceDatabase();

            sequenceDatabase.loadLinkedList(generateLinkedList(courses, users, types, minLength, maxLength,
                    startTime, endTime, session, gender));

            final AlgoFournierViger08 algo = new AlgoFournierViger08(minSup, 0L, 1L, 0L, 1000L, null, true, false);

            // execute the algorithm
            final Clock c = new Clock();
            final Sequences res = algo.runAlgorithm(sequenceDatabase);
            logger.debug("Time for Hirate-calculation: " + c.get());

            final LinkedHashMap<String, UserPathObject> pathObjects = Maps.newLinkedHashMap();
            Long pathId = 0L;
            for (int i = res.getLevelCount() - 1; i >= 0; i--) {
                for (int j = 0; j < res.getLevel(i).size(); j++) {
                    String predecessor = null;
                    final Long absSup = Long.valueOf(res.getLevel(i).get(j).getAbsoluteSupport());
                    pathId++;
                    logger.debug(
                            "New " + i + "-Sequence. Support : " + res.getLevel(i).get(j).getAbsoluteSupport());
                    for (int k = 0; k < res.getLevel(i).get(j).size(); k++) {

                        final String obId = this.internalIdToId
                                .get(res.getLevel(i).get(j).get(k).getItems().get(0).getId());

                        final ILogMining ilo = this.idToLogM.get(obId);

                        final String type = ilo.getClass().getSimpleName();

                        final String posId = String.valueOf(pathObjects.size());

                        if (predecessor != null) {
                            pathObjects.put(posId,
                                    new UserPathObject(posId, ilo.getTitle(), absSup, type,
                                            Double.valueOf(ilo.getDuration()), ilo.getPrefix(), pathId,
                                            Long.valueOf(this.requests.get(obId).size()),
                                            Long.valueOf(new HashSet<Long>(this.requests.get(obId)).size())));

                            // Increment or create predecessor edge
                            pathObjects.get(predecessor).addEdgeOrIncrement(posId);
                        } else {
                            pathObjects.put(posId,
                                    new UserPathObject(posId, ilo.getTitle(), absSup, type,
                                            Double.valueOf(ilo.getDuration()), ilo.getPrefix(), pathId,
                                            Long.valueOf(this.requests.get(obId).size()),
                                            Long.valueOf(new HashSet<Long>(this.requests.get(obId)).size())));
                        }
                        predecessor = posId;
                    }

                }
            }
            logger.debug("\n");

            for (final UserPathObject pathEntry : pathObjects.values()) {

                final UserPathObject path = pathEntry;
                path.setWeight(path.getWeight());
                path.setPathId(pathEntry.getPathId());
                nodes.add(new UserPathNode(path, true));
                final String sourcePos = path.getId();

                for (final Entry<String, Integer> linkEntry : pathEntry.getEdges().entrySet()) {
                    final UserPathLink link = new UserPathLink();
                    link.setSource(sourcePos);
                    link.setPathId(path.getPathId());
                    link.setTarget(linkEntry.getKey());
                    link.setValue(String.valueOf(linkEntry.getValue()));
                    links.add(link);
                }
            }

        } catch (final Exception e) {
            logger.error(e.getMessage());

        } finally {
            this.requests.clear();
            this.idToLogM.clear();
            this.internalIdToId.clear();
            this.idToInternalId.clear();
        }
        session.close();
        return new ResultListUserPathGraph(nodes, links);
    }

    /**
     * Generates the necessary list of input-strings, containing the sequences (user paths) for the BIDE+ algorithm
     * 
     * @param courses
     *            Course-Ids
     * @param users
     *            User-Ids
     * @param starttime
     *            Start time
     * @param endtime
     *            End time
     * @return The path to the generated file
     */
    @SuppressWarnings("unchecked")
    private LinkedList<String> generateLinkedList(final List<Long> courses, List<Long> users,
            final List<String> types, final Long minLength, final Long maxLength, final Long starttime,
            final Long endtime, Session session, List<Long> gender) {
        final LinkedList<String> result = new LinkedList<String>();
        final boolean hasBorders = (minLength != null) && (maxLength != null) && (maxLength > 0)
                && (minLength < maxLength);
        final boolean hasTypes = (types != null) && (types.size() > 0);

        Criteria criteria;
        if (users == null || users.size() == 0) {
            users = new ArrayList<Long>(StudentHelper.getCourseStudentsAliasKeys(courses, gender).values());
        } else {
            Map<Long, Long> userMap = StudentHelper.getCourseStudentsAliasKeys(courses, gender);
            List<Long> tmp = new ArrayList<Long>();
            for (int i = 0; i < users.size(); i++) {
                tmp.add(userMap.get(users.get(i)));
            }
            users = tmp;
        }

        criteria = session.createCriteria(ILogMining.class, "log");
        if (courses.size() > 0) {
            criteria.add(Restrictions.in("log.course.id", courses));
        }
        if (users.size() > 0) {
            criteria.add(Restrictions.in("log.user.id", users));
        }
        criteria.add(Restrictions.between("log.timestamp", starttime, endtime));
        final ArrayList<ILogMining> list = (ArrayList<ILogMining>) criteria.list();

        logger.debug("Read " + list.size() + " logs.");

        int max = 0;

        final HashMap<Long, ArrayList<ILogMining>> logMap = new HashMap<Long, ArrayList<ILogMining>>();

        for (int i = 0; i < list.size(); i++) {
            if ((list.get(i).getUser() != null) && (list.get(i).getLearnObjId() != null)) {
                // If there isn't a user history for this user-id create a new one
                if (logMap.get(list.get(i).getUser().getId()) == null) {
                    // User histories are saved in an ArrayList of ILogMining-objects
                    final ArrayList<ILogMining> a = new ArrayList<ILogMining>();
                    // Add current ILogMining-object to user-history
                    a.add(list.get(i));
                    // Add user history to the user history map
                    logMap.put(list.get(i).getUser().getId(), a);
                } else {
                    // Add current ILogMining-object to user-history
                    logMap.get(list.get(i).getUser().getId()).add(list.get(i));
                    // Sort the user's history (by time stamp)
                    Collections.sort(logMap.get(list.get(i).getUser().getId()));
                }
            }
        }

        // Just changing the container for the user histories
        final ArrayList<ArrayList<ILogMining>> uhis = new ArrayList<ArrayList<ILogMining>>();
        int id = 1;
        for (final ArrayList<ILogMining> uLog : logMap.values()) {

            final ArrayList<ILogMining> tmp = new ArrayList<ILogMining>();
            boolean containsType = false;
            for (final ILogMining iLog : uLog) {
                if (this.idToInternalId.get(iLog.getPrefix() + " " + iLog.getLearnObjId()) == null) {
                    this.internalIdToId.put(id, iLog.getPrefix() + " " + iLog.getLearnObjId());
                    this.idToInternalId.put(iLog.getPrefix() + " " + iLog.getLearnObjId(), id);
                    id++;
                }
                if (hasTypes) {
                    for (final String type : types) {
                        if (iLog.getClass().getSimpleName().toLowerCase().contains(type.toLowerCase())) {
                            containsType = true;
                            tmp.add(iLog);
                            break;
                        }

                    }
                }
                if (!hasTypes) {
                    tmp.add(iLog);
                }
            }
            if ((!hasBorders || ((tmp.size() >= minLength) && (tmp.size() <= maxLength)))
                    && (!hasTypes || containsType)) {
                uhis.add(tmp);
                if (tmp.size() > max) {
                    max = tmp.size();
                }
            }
        }

        // This part is only for statistics - group histories of similar length together and display there
        // respective lengths
        final Integer[] lengths = new Integer[(max / 10) + 1];
        for (int i = 0; i < lengths.length; i++) {
            lengths[i] = 0;
        }

        for (int i = 0; i < uhis.size(); i++) {
            lengths[uhis.get(i).size() / 10]++;
        }

        for (int i = 0; i < lengths.length; i++) {
            if (lengths[i] != 0) {
                logger.debug("Paths of length " + i + "0 - " + (i + 1) + "0: " + lengths[i]);
            }
        }

        logger.debug("Generated " + uhis.size() + " user histories. Max length @ " + max);

        int z = 0;

        // Convert all user histories or "paths" into the format, that is requested by the BIDE-algorithm-class
        for (final ArrayList<ILogMining> l : uhis) {
            String line = "";
            for (int i = 0; i < l.size(); i++) {
                if (this.idToLogM.get(l.get(i).getPrefix() + " " + l.get(i).getLearnObjId()) == null) {
                    this.idToLogM.put(l.get(i).getPrefix() + " " + l.get(i).getLearnObjId(), l.get(i));
                }

                // Update request numbers
                if (this.requests.get(l.get(i).getPrefix() + " " + l.get(i).getLearnObjId()) == null) {
                    final ArrayList<Long> us = new ArrayList<Long>();
                    us.add(l.get(i).getUser().getId());
                    this.requests.put(l.get(i).getPrefix() + " " + l.get(i).getLearnObjId(), us);
                } else {
                    this.requests.get(l.get(i).getPrefix() + " " + l.get(i).getLearnObjId())
                            .add(l.get(i).getUser().getId());
                }
                // The id of the object gets the prefix, indicating it's class. This is important for distinction
                // between objects of different ILogMining-classes but same ids
                line += "<" + i + "> "
                        + this.idToInternalId.get(l.get(i).getPrefix() + " " + l.get(i).getLearnObjId()) + " -1 ";
            }
            line += "-2";
            result.add(line);
            z++;
        }
        logger.debug("Wrote " + z + " logs.");
        return result;
    }

}