org.unitime.timetable.backup.SessionBackup.java Source code

Java tutorial

Introduction

Here is the source code for org.unitime.timetable.backup.SessionBackup.java

Source

/*
 * UniTime 3.3 - 3.5 (University Timetabling Application)
 * Copyright (C) 2011 - 2013, UniTime LLC, and individual contributors
 * as indicated by the @authors tag.
 * 
 * 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
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU 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/>.
 * 
*/
package org.unitime.timetable.backup;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.Serializable;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Queue;
import java.util.Set;
import java.util.TreeSet;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.log4j.PropertyConfigurator;
import org.cpsolver.ifs.util.Progress;
import org.cpsolver.ifs.util.ProgressWriter;
import org.dom4j.Document;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;
import org.hibernate.CacheMode;
import org.hibernate.SessionFactory;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.metadata.ClassMetadata;
import org.hibernate.type.BinaryType;
import org.hibernate.type.CollectionType;
import org.hibernate.type.CustomType;
import org.hibernate.type.DateType;
import org.hibernate.type.EmbeddedComponentType;
import org.hibernate.type.EntityType;
import org.hibernate.type.PrimitiveType;
import org.hibernate.type.StringType;
import org.hibernate.type.TimestampType;
import org.hibernate.type.Type;
import org.unitime.commons.hibernate.util.HibernateUtil;
import org.unitime.timetable.ApplicationProperties;
import org.unitime.timetable.model.Assignment;
import org.unitime.timetable.model.AssignmentInfo;
import org.unitime.timetable.model.ChangeLog;
import org.unitime.timetable.model.ConstraintInfo;
import org.unitime.timetable.model.CurriculumClassification;
import org.unitime.timetable.model.DepartmentalInstructor;
import org.unitime.timetable.model.DistributionPref;
import org.unitime.timetable.model.DistributionType;
import org.unitime.timetable.model.LastLikeCourseDemand;
import org.unitime.timetable.model.OnlineSectioningLog;
import org.unitime.timetable.model.Session;
import org.unitime.timetable.model.Solution;
import org.unitime.timetable.model.SolutionInfo;
import org.unitime.timetable.model.Student;
import org.unitime.timetable.model.TimetableManager;
import org.unitime.timetable.model.dao._RootDAO;

import com.google.protobuf.ByteString;
import com.google.protobuf.CodedOutputStream;

/**
 * @author Tomas Muller
 */
public class SessionBackup implements SessionBackupInterface {
    private static Log sLog = LogFactory.getLog(SessionBackup.class);
    private SessionFactory iHibSessionFactory = null;
    private org.hibernate.Session iHibSession = null;

    private CodedOutputStream iOut = null;
    private PrintWriter iDebug = null;
    private Long iSessionId = null;
    private Progress iProgress = null;

    public Progress getProgress() {
        return iProgress;
    }

    private void add(TableData.Table table) throws IOException {
        iProgress.info("Writing " + table.getName().substring(table.getName().lastIndexOf('.') + 1) + " ["
                + table.getRecordCount() + " records, " + table.getSerializedSize() + " bytes]");
        iOut.writeInt32NoTag(table.getSerializedSize());
        table.writeTo(iOut);
        iOut.flush();
        if (iDebug != null) {
            iDebug.println("## " + table.getName() + " ##");
            iDebug.print(table.toString());
            iDebug.flush();
        }
    }

    public void debug(PrintWriter pw) {
        iDebug = pw;
    }

    @Override
    public void backup(OutputStream out, Progress progress, Long sessionId) throws IOException {
        iOut = CodedOutputStream.newInstance(out);
        iProgress = progress;
        iSessionId = sessionId;
        iHibSession = new _RootDAO().createNewSession();
        iHibSession.setCacheMode(CacheMode.IGNORE);
        iHibSessionFactory = iHibSession.getSessionFactory();
        try {
            iProgress.setStatus("Exporting Session");
            iProgress.setPhase("Loading Model", 3);
            TreeSet<ClassMetadata> allMeta = new TreeSet<ClassMetadata>(new Comparator<ClassMetadata>() {
                @Override
                public int compare(ClassMetadata m1, ClassMetadata m2) {
                    return m1.getEntityName().compareTo(m2.getEntityName());
                }
            });
            allMeta.addAll(iHibSessionFactory.getAllClassMetadata().values());
            iProgress.incProgress();

            Queue<QueueItem> queue = new LinkedList<QueueItem>();

            queue.add(new QueueItem(iHibSessionFactory.getClassMetadata(Session.class), null, "uniqueId",
                    Relation.None));

            Set<String> avoid = new HashSet<String>();
            // avoid following relations
            avoid.add(TimetableManager.class.getName() + ".departments");
            avoid.add(TimetableManager.class.getName() + ".solverGroups");
            avoid.add(DistributionType.class.getName() + ".departments");
            avoid.add(LastLikeCourseDemand.class.getName() + ".student");
            avoid.add(Student.class.getName() + ".lastLikeCourseDemands");

            Set<String> disallowedNotNullRelations = new HashSet<String>();
            disallowedNotNullRelations.add(Assignment.class.getName() + ".datePattern");
            disallowedNotNullRelations.add(Assignment.class.getName() + ".timePattern");
            disallowedNotNullRelations.add(LastLikeCourseDemand.class.getName() + ".student");
            disallowedNotNullRelations.add(OnlineSectioningLog.class.getName() + ".session");

            Map<String, List<QueueItem>> data = new HashMap<String, List<QueueItem>>();
            List<QueueItem> sessions = new ArrayList<QueueItem>();
            sessions.add(queue.peek());
            data.put(queue.peek().name(), sessions);

            QueueItem item = null;
            while ((item = queue.poll()) != null) {
                if (item.size() == 0)
                    continue;
                for (ClassMetadata meta : allMeta) {
                    if (meta.hasSubclasses())
                        continue;
                    for (int i = 0; i < meta.getPropertyNames().length; i++) {
                        String property = meta.getPropertyNames()[i];
                        if (disallowedNotNullRelations.contains(meta.getEntityName() + "." + property)
                                || meta.getPropertyNullability()[i])
                            continue;
                        Type type = meta.getPropertyTypes()[i];
                        if (type instanceof EntityType && type.getReturnedClass().equals(item.clazz())) {
                            QueueItem qi = new QueueItem(meta, item, property, Relation.Parent);
                            if (!data.containsKey(qi.name())) {
                                List<QueueItem> items = new ArrayList<QueueItem>();
                                data.put(qi.name(), items);
                                queue.add(qi);
                                items.add(qi);
                                if (qi.size() > 0)
                                    iProgress.info("Parent: " + qi);
                            }
                        }
                    }
                }
            }
            iProgress.incProgress();

            for (List<QueueItem> list : data.values())
                queue.addAll(list);

            // The following part is needed to ensure that instructor distribution preferences are saved including their distribution types 
            List<QueueItem> distributions = new ArrayList<QueueItem>();
            for (QueueItem instructor : data.get(DepartmentalInstructor.class.getName())) {
                QueueItem qi = new QueueItem(iHibSessionFactory.getClassMetadata(DistributionPref.class),
                        instructor, "owner", Relation.Parent);
                distributions.add(qi);
                queue.add(qi);
                if (qi.size() > 0)
                    iProgress.info("Extra: " + qi);
            }
            data.put(DistributionPref.class.getName(), distributions);

            while ((item = queue.poll()) != null) {
                if (item.size() == 0)
                    continue;
                for (int i = 0; i < item.meta().getPropertyNames().length; i++) {
                    String property = item.meta().getPropertyNames()[i];
                    Type type = item.meta().getPropertyTypes()[i];
                    if (type instanceof EntityType) {
                        if (avoid.contains(item.name() + "." + property))
                            continue;

                        ClassMetadata meta = iHibSessionFactory.getClassMetadata(type.getReturnedClass());
                        if (item.contains(meta.getEntityName()))
                            continue;

                        QueueItem qi = new QueueItem(meta, item, property, Relation.One);
                        List<QueueItem> items = data.get(qi.name());
                        if (items == null) {
                            items = new ArrayList<QueueItem>();
                            data.put(qi.name(), items);
                        }
                        queue.add(qi);
                        items.add(qi);

                        if (qi.size() > 0)
                            iProgress.info("One: " + qi);
                    }
                    if (type instanceof CollectionType) {
                        if (avoid.contains(item.name() + "." + property))
                            continue;

                        ClassMetadata meta = iHibSessionFactory.getClassMetadata(((CollectionType) type)
                                .getElementType((SessionFactoryImplementor) iHibSessionFactory).getReturnedClass());
                        if (meta == null || item.contains(meta.getEntityName()))
                            continue;

                        QueueItem qi = new QueueItem(meta, item, property, Relation.Many);
                        List<QueueItem> items = data.get(qi.name());
                        if (items == null) {
                            items = new ArrayList<QueueItem>();
                            data.put(qi.name(), items);
                        }
                        queue.add(qi);
                        items.add(qi);

                        if (qi.size() > 0)
                            iProgress.info("Many: " + qi);
                    }
                }
            }
            iProgress.incProgress();

            Map<String, Set<Serializable>> allExportedIds = new HashMap<String, Set<Serializable>>();
            for (String name : new TreeSet<String>(data.keySet())) {
                List<QueueItem> list = data.get(name);
                Map<String, TableData.Table.Builder> tables = new HashMap<String, TableData.Table.Builder>();
                for (QueueItem current : list) {
                    if (current.size() == 0)
                        continue;
                    iProgress.info("Loading " + current);
                    List<Object> objects = current.list();
                    if (objects == null || objects.isEmpty())
                        continue;
                    iProgress.setPhase(current.abbv() + " [" + objects.size() + "]", objects.size());
                    objects: for (Object object : objects) {
                        iProgress.incProgress();

                        // Get meta data (check for sub-classes)
                        ClassMetadata meta = iHibSessionFactory.getClassMetadata(object.getClass());
                        if (meta == null)
                            meta = current.meta();
                        if (meta.hasSubclasses()) {
                            for (Iterator i = iHibSessionFactory.getAllClassMetadata().entrySet().iterator(); i
                                    .hasNext();) {
                                Map.Entry entry = (Map.Entry) i.next();
                                ClassMetadata classMetadata = (ClassMetadata) entry.getValue();
                                if (classMetadata.getMappedClass().isInstance(object)
                                        && !classMetadata.hasSubclasses()) {
                                    meta = classMetadata;
                                    break;
                                }
                            }
                        }

                        // Get unique identifier
                        Serializable id = meta.getIdentifier(object, (SessionImplementor) iHibSession);

                        // Check if already exported
                        Set<Serializable> exportedIds = allExportedIds.get(meta.getEntityName());
                        if (exportedIds == null) {
                            exportedIds = new HashSet<Serializable>();
                            allExportedIds.put(meta.getEntityName(), exportedIds);
                        }
                        if (!exportedIds.add(id))
                            continue;

                        // Check relation to an academic session (if exists)
                        for (String property : meta.getPropertyNames()) {
                            Type type = meta.getPropertyType(property);
                            if (type instanceof EntityType && type.getReturnedClass().equals(Session.class)) {
                                Session s = (Session) meta.getPropertyValue(object, property);
                                if (s != null && !s.getUniqueId().equals(iSessionId)) {
                                    iProgress.warn(meta.getEntityName()
                                            .substring(meta.getEntityName().lastIndexOf('.') + 1) + "@" + id
                                            + " belongs to a different academic session (" + s + ")");
                                    continue objects; // wrong session
                                }
                            }
                        }

                        // Get appropriate table
                        TableData.Table.Builder table = tables.get(meta.getEntityName());
                        if (table == null) {
                            table = TableData.Table.newBuilder();
                            tables.put(meta.getEntityName(), table);
                            table.setName(meta.getEntityName());
                        }

                        // Export object
                        TableData.Record.Builder record = TableData.Record.newBuilder();
                        record.setId(id.toString());
                        for (String property : meta.getPropertyNames()) {
                            Type type = meta.getPropertyType(property);
                            Object value = meta.getPropertyValue(object, property);
                            if (value == null)
                                continue;
                            TableData.Element.Builder element = TableData.Element.newBuilder();
                            element.setName(property);
                            if (type instanceof PrimitiveType) {
                                element.addValue(((PrimitiveType) type).toString(value));
                            } else if (type instanceof StringType) {
                                element.addValue(((StringType) type).toString((String) value));
                            } else if (type instanceof BinaryType) {
                                element.addValueBytes(ByteString.copyFrom((byte[]) value));
                            } else if (type instanceof TimestampType) {
                                element.addValue(((TimestampType) type).toString((Date) value));
                            } else if (type instanceof DateType) {
                                element.addValue(((DateType) type).toString((Date) value));
                            } else if (type instanceof EntityType) {
                                List<Object> ids = current.relation(property, id, false);
                                if (ids != null)
                                    for (Object i : ids)
                                        element.addValue(i.toString());
                                iHibSession.evict(value);
                            } else if (type instanceof CustomType && value instanceof Document) {
                                if (object instanceof CurriculumClassification && property.equals("students"))
                                    continue;
                                StringWriter w = new StringWriter();
                                XMLWriter x = new XMLWriter(w, OutputFormat.createCompactFormat());
                                x.write((Document) value);
                                x.flush();
                                x.close();
                                element.addValue(w.toString());
                            } else if (type instanceof CollectionType) {
                                List<Object> ids = current.relation(property, id, false);
                                if (ids != null)
                                    for (Object i : ids)
                                        element.addValue(i.toString());
                            } else if (type instanceof EmbeddedComponentType
                                    && property.equalsIgnoreCase("uniqueCourseNbr")) {
                                continue;
                            } else {
                                iProgress.warn("Unknown data type: " + type + " (property " + meta.getEntityName()
                                        + "." + property + ", class " + value.getClass() + ")");
                                continue;
                            }
                            record.addElement(element.build());

                        }
                        table.addRecord(record.build());
                        iHibSession.evict(object);
                    }
                    current.clearCache();
                }

                for (TableData.Table.Builder table : tables.values()) {
                    add(table.build());
                }
            }

            /*
            // Skip ConstraintInfo
            if (!iData.containsKey(ConstraintInfo.class.getName()))
               iData.put(ConstraintInfo.class.getName(), new QueueItem(iHibSessionFactory.getClassMetadata(ConstraintInfo.class), null, null, Relation.Empty));
                
            for (String name: items)
               export(iData.get(name));
                    
            while (true) {
             List<Object> objects = new ArrayList<Object>();
             ClassMetadata meta = null;
             for (Entity e: iObjects) {
            if (e.exported()) continue;
            if (objects.isEmpty() || meta.getEntityName().equals(e.name())) {
               meta = e.meta();
               objects.add(e.object());
               e.notifyExported();
            }
             }
             if (objects.isEmpty()) break;
             export(meta, objects, null);
            }
            */
            iProgress.setStatus("All done.");
        } finally {
            iHibSession.close();
        }
    }

    enum Relation {
        None, Parent, One, Many, Empty
    }

    private int iQueueItemCoutner = 0;
    private Map<String, Set<Serializable>> iExclude = new HashMap<String, Set<Serializable>>();

    class QueueItem {
        QueueItem iParent;
        ClassMetadata iMeta;
        String iProperty;
        int iQueueItemId = iQueueItemCoutner++;
        Relation iRelation;
        int size = -1;

        QueueItem(ClassMetadata meta, QueueItem parent, String property, Relation relation) {
            iMeta = meta;
            iParent = parent;
            iProperty = property;
            iRelation = relation;
        }

        String property() {
            return iProperty;
        }

        QueueItem parent() {
            return iParent;
        }

        ClassMetadata meta() {
            return iMeta;
        }

        String name() {
            return meta().getEntityName();
        }

        String abbv() {
            return meta().getEntityName().substring(meta().getEntityName().lastIndexOf('.') + 1);
        }

        Class clazz() {
            return meta().getMappedClass();
        }

        int qid() {
            return iQueueItemId;
        }

        Relation relation() {
            return iRelation;
        }

        boolean contains(String name) {
            if (name.equals(name()))
                return true;
            if (parent() == null)
                return false;
            return parent().contains(name);
        }

        int depth() {
            return parent() == null ? 0 : 1 + parent().depth();
        }

        public String toString() {
            return abbv() + ": " + chain() + " [" + size() + "]";
        }

        public String chain() {
            switch (relation()) {
            case None:
                return property();
            case Parent:
                return property() + "." + parent().chain();
            default:
                switch (parent().relation()) {
                case Parent:
                    return "(" + parent().abbv() + "." + parent().chain() + ")." + property();
                case None:
                    return parent().abbv() + "." + property();
                default:
                    return parent().chain() + "." + property();
                }
            }
        }

        String hqlName() {
            return "q" + qid();
        }

        String hqlFrom() {
            switch (relation()) {
            case One:
            case Many:
                return parent().hqlFrom() + " inner join " + parent().hqlName() + "." + property() + " "
                        + hqlName();
            default:
                return (parent() == null ? "" : parent().hqlFrom() + ", ") + name() + " " + hqlName();
            }
        }

        String hqlWhere() {
            String where = null;
            switch (relation()) {
            case None:
                where = hqlName() + "." + property() + " = :sessionId";
                break;
            case Parent:
                where = hqlName() + "." + property() + " = " + parent().hqlName() + " and " + parent().hqlWhere();
                break;
            default:
                where = parent().hqlWhere();
                break;
            }
            if (Solution.class.getName().equals(name()))
                where += " and " + hqlName() + ".commited = true";
            if (Assignment.class.getName().equals(name()))
                where += " and " + hqlName() + ".solution.commited = true";
            if (SolutionInfo.class.getName().equals(name()))
                where += " and " + hqlName() + ".definition.name = 'GlobalInfo'";
            return where;
        }

        int size() {
            if (relation() == Relation.Empty)
                return 0;
            if (AssignmentInfo.class.getName().equals(name()))
                return 0;
            if (ConstraintInfo.class.getName().equals(name()))
                return 0;
            if (ChangeLog.class.getName().equals(name()))
                return 0;
            if (size < 0) {
                Set<Serializable> ids = iExclude.get(name());
                if (ids == null) {
                    ids = new HashSet<Serializable>();
                    iExclude.put(name(), ids);
                }
                size = 0;
                for (Serializable id : (List<Serializable>) iHibSession
                        .createQuery("select distinct " + hqlName() + "." + meta().getIdentifierPropertyName()
                                + " from " + hqlFrom() + " where " + hqlWhere())
                        .setLong("sessionId", iSessionId).list()) {
                    if (ids.add(id))
                        size++;
                }
            }
            return size;
        }

        boolean hasBlob() {
            for (String property : iMeta.getPropertyNames()) {
                if (iMeta.getPropertyType(property) instanceof BinaryType)
                    return true;
            }
            return false;
        }

        boolean distinct() {
            if (hasBlob())
                return true;
            switch (relation()) {
            case Many:
                return false;
            case One:
                return parent().distinct();
            default:
                return true;
            }
        }

        List<Object> list() {
            if (relation() == Relation.Empty)
                return null;
            if (AssignmentInfo.class.getName().equals(name()))
                return null;
            if (ConstraintInfo.class.getName().equals(name()))
                return null;
            if (ChangeLog.class.getName().equals(name()))
                return null;
            return iHibSession.createQuery("select " + (distinct() ? "" : "distinct ") + hqlName() + " from "
                    + hqlFrom() + " where " + hqlWhere()).setLong("sessionId", iSessionId).list();
        }

        Map<String, Map<Serializable, List<Object>>> iRelationCache = new HashMap<String, Map<Serializable, List<Object>>>();

        List<Object> relation(String property, Serializable id, boolean data) {
            Map<Serializable, List<Object>> relation = iRelationCache.get(property);
            if (relation == null) {
                Type type = meta().getPropertyType(property);
                String idProperty = null;
                if (!data) {
                    ClassMetadata meta = null;
                    if (type instanceof CollectionType)
                        meta = iHibSessionFactory.getClassMetadata(((CollectionType) type)
                                .getElementType((SessionFactoryImplementor) iHibSessionFactory).getReturnedClass());
                    else
                        meta = iHibSessionFactory.getClassMetadata(type.getReturnedClass());
                    if (meta == null) {
                        data = true;
                    } else {
                        idProperty = meta.getIdentifierPropertyName();
                        if (name().equals(LastLikeCourseDemand.class.getName()) && "student".equals(property))
                            idProperty = "externalUniqueId";
                    }
                }
                relation = new HashMap<Serializable, List<Object>>();
                for (Object[] o : (List<Object[]>) iHibSession
                        .createQuery("select distinct " + hqlName() + "." + meta().getIdentifierPropertyName()
                                + (data ? ", p" : ", p." + idProperty) + " from " + hqlFrom() + " inner join "
                                + hqlName() + "." + property + " p where " + hqlWhere())
                        .setLong("sessionId", iSessionId).list()) {
                    List<Object> list = relation.get((Serializable) o[0]);
                    if (list == null) {
                        list = new ArrayList<Object>();
                        relation.put((Serializable) o[0], list);
                    }
                    list.add(o[1]);
                }
                iRelationCache.put(property, relation);
                // iProgress.info("Fetched " + property + " (" + cnt + (data ? " items" : " ids") + ")");
            }
            return relation.get(id);
        }

        private void clearCache() {
            iRelationCache.clear();
        }
    }

    public static void main(String[] args) {
        try {
            Properties props = new Properties();
            props.setProperty("log4j.rootLogger", "DEBUG, A1");
            props.setProperty("log4j.appender.A1", "org.apache.log4j.ConsoleAppender");
            props.setProperty("log4j.appender.A1.layout", "org.apache.log4j.PatternLayout");
            props.setProperty("log4j.appender.A1.layout.ConversionPattern", "%-5p %m%n");
            props.setProperty("log4j.logger.org.hibernate", "INFO");
            props.setProperty("log4j.logger.org.hibernate.cfg", "WARN");
            props.setProperty("log4j.logger.org.hibernate.cache.EhCacheProvider", "ERROR");
            props.setProperty("log4j.logger.org.unitime.commons.hibernate", "INFO");
            props.setProperty("log4j.logger.net", "INFO");
            PropertyConfigurator.configure(props);

            HibernateUtil.configureHibernate(ApplicationProperties.getProperties());

            Session session = Session.getSessionUsingInitiativeYearTerm(
                    ApplicationProperties.getProperty("initiative", "PWL"),
                    ApplicationProperties.getProperty("year", "2012"),
                    ApplicationProperties.getProperty("term", "Spring"));

            if (session == null) {
                sLog.error(
                        "Academic session not found, use properties initiative, year, and term to set academic session.");
                System.exit(0);
            } else {
                sLog.info("Session: " + session);
            }

            FileOutputStream out = new FileOutputStream(
                    args.length == 0
                            ? session.getAcademicTerm() + session.getAcademicYear()
                                    + session.getAcademicInitiative() + ".dat"
                            : args[0]);

            Progress progress = Progress.getInstance();

            SessionBackup backup = new SessionBackup();

            PrintWriter debug = null;
            if (args.length >= 2) {
                debug = new PrintWriter(args[1]);
                backup.debug(debug);
            }

            progress.addProgressListener(new ProgressWriter(System.out));

            backup.backup(out, progress, session.getUniqueId());

            out.close();
            if (debug != null)
                debug.close();

        } catch (Exception e) {
            sLog.fatal("Backup failed: " + e.getMessage(), e);
        }
    }

}