org.olat.upgrade.OLATUpgrade_6_3_0.java Source code

Java tutorial

Introduction

Here is the source code for org.olat.upgrade.OLATUpgrade_6_3_0.java

Source

/**
 * OLAT - Online Learning and Training<br>
 * http://www.olat.org
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License"); <br>
 * you may not use this file except in compliance with the License.<br>
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing,<br>
 * software distributed under the License is distributed on an "AS IS" BASIS, <br>
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
 * See the License for the specific language governing permissions and <br>
 * limitations under the License.
 * <p>
 * Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br>
 * University of Zurich, Switzerland.
 * <p>
 */

package org.olat.upgrade;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import org.apache.commons.io.filefilter.RegexFileFilter;
import org.hibernate.FlushMode;
import org.hibernate.ObjectDeletedException;
import org.olat.core.CoreBeanTypes;
import org.olat.core.CoreSpringFactory;
import org.olat.core.commons.modules.bc.FolderConfig;
import org.olat.core.commons.persistence.DBFactory;
import org.olat.core.commons.persistence.DBQuery;
import org.olat.core.commons.services.text.TextService;
import org.olat.core.util.FileUtils;
import org.olat.core.util.StringHelper;
import org.olat.core.util.WebappHelper;
import org.olat.core.util.ZipUtil;
import org.olat.core.util.notifications.NotificationsManager;
import org.olat.core.util.notifications.NotificationsUpgrade;
import org.olat.core.util.notifications.Publisher;
import org.olat.core.util.notifications.Subscriber;
import org.olat.core.util.resource.OresHelper;
import org.olat.course.CourseModule;
import org.olat.course.PersistingCourseImpl;
import org.olat.course.statistic.LoggingVersionManagerImpl;
import org.olat.modules.fo.ForumManager;
import org.olat.modules.fo.Message;
import org.olat.repository.RepositoryEntry;
import org.olat.repository.RepositoryManager;
import org.olat.repository.delete.service.DeletionModule;
import org.springframework.beans.factory.annotation.Autowired;

/**
 * Description:<br>
 * Upgrade to OLAT 6.2: - Migration of old wiki-fields to flexiform Code is already here for every update. Method calls will be commented out step by step when
 * corresponding new controllers are ready. As long as there will be other things to migrate Upgrade won't be set to DONE!
 * <P>
 * Initial Date: 20.06.09 <br>
 * 
 * @author Roman Haag, roman.haag@frentix.com, www.frentix.com
 */
public class OLATUpgrade_6_3_0 extends OLATUpgrade {
    private static final String VERSION = "OLAT_6.3";
    private static final String TASK_CLEANUP_TMP_UPLOAD_FILES_KEY = "cleanupTmpUploadFiles";
    private static final String TASK_MIGRATE_FORUMS_MESSAGES = "Migrate forums messages to add word and character count";
    private static final String TASK_MIGRATE_NOTIFICATIONS = "Migrate notifications publishers";
    private static final String TASK_MIGRATE_COURSE_LOG_FILES = "Migrate course log files to course folders and deleted dir";

    /** filename used to store courseauthor's activities (personalized) - relict from PersistingAuditManager **/
    public static final String FILENAME_ADMIN_LOG = "course_admin_log.txt";

    /** readme filename used by old CourseLogsArchiveManager - relict from CourseLogsArchiveManager **/
    private static final String README = "README.txt";

    /**
     * filename used to store all user's activities (personalized) in the course only visible for OLAT-admins - relict from PersistingAuditManager
     */
    public static final String FILENAME_USER_LOG = "course_user_log.txt";
    /** filename used to store all user's activities (anonymised) in the course - relict from PersistingAuditManager **/
    public static final String FILENAME_STATISTIC_LOG = "course_statistic_log.txt";

    /** from PersistingCourseImpl which has this as private unfortunatelly **/
    private static final String COURSEFOLDER = "coursefolder";

    private static final String LOGS_DIRNAME = "logs";

    private static final String OLD_COURSE_LOGS_DIRNAME = "old_course_logs";
    private static final String OLD_COURSE_LOGS_ZIPFILENAME = "old_course_logs.zip";
    private static final String OLD_COURSE_LOGS_IN_APACHE_FORMAT_ZIPFILENAME = "old_course_logs_apache_format.zip";
    private static final String TASK_CLEANUP_BROKEN_COURSES = "cleanup_broken_courses";

    private Map<String, NotificationsUpgrade> notificationUpgrades;

    /** counter for statistics about what went wrong during apache course log migration **/
    private int filesWithApacheConversionErrors_ = 0;
    private final DeletionModule deletionModule;
    private final CourseModule courseModule;
    private final String nodeId;
    private final Object lockObject = new Object();
    @Autowired
    TextService languageService;

    /**
     * [used by spring]
     */
    public OLATUpgrade_6_3_0(final DeletionModule deletionModule, final CourseModule courseModule,
            final String nodeId) {
        this.deletionModule = deletionModule;
        this.courseModule = courseModule;
        this.nodeId = nodeId;
    }

    /**
     * @see org.olat.upgrade.OLATUpgrade#doPreSystemInitUpgrade(org.olat.upgrade.UpgradeManager)
     */
    public boolean doPreSystemInitUpgrade(final UpgradeManager upgradeManager) {
        return false;
    }

    /**
     * @see org.olat.upgrade.OLATUpgrade#doPostSystemInitUpgrade(org.olat.upgrade.UpgradeManager)
     */
    public boolean doPostSystemInitUpgrade(final UpgradeManager upgradeManager) {
        UpgradeHistoryData uhd = upgradeManager.getUpgradesHistory(VERSION);
        if (uhd == null) {
            // has never been called, initialize
            uhd = new UpgradeHistoryData();
        } else {
            if (uhd.isInstallationComplete()) {
                return false;
            }
        }

        // Cleanup temp upload files that are not deleted properly
        cleanupTmpUploadFiles(upgradeManager, uhd);
        // Migrate forums messages
        migrateMessages(upgradeManager, uhd);
        // Migrate notifications
        migrateNotifications(upgradeManager, uhd);
        // Migrate course log files
        migrateCourseLogFiles(upgradeManager, uhd);
        // check and fix broken courses on the filesystem and database
        searchForBrokenCourses(upgradeManager, uhd);

        // set the logging version to 1 starting NOW
        new LoggingVersionManagerImpl().setLoggingVersionStartingNow(1);

        // // now pre and post code was ok, finish installation
        uhd.setInstallationComplete(true);
        // // persist infos
        upgradeManager.setUpgradesHistory(uhd, VERSION);
        return true;
    }

    private void searchForBrokenCourses(final UpgradeManager upgradeManager, final UpgradeHistoryData uhd) {
        if (!uhd.getBooleanDataValue(TASK_CLEANUP_BROKEN_COURSES)) {

            final String bcRoot = FolderConfig.getCanonicalRoot();
            final File courseFolder = new File(bcRoot + "/course");
            final String[] courseFolderNames = courseFolder.list(new FilenameFilter() {

                @Override
                public boolean accept(final File dir, final String name) {
                    try {
                        Long.parseLong(name);
                    } catch (final NumberFormatException e) {
                        return false;
                    }
                    return true;
                }
            });
            final List<String> courseList = Arrays.asList(courseFolderNames);
            int counter = 0;
            for (final String string : courseList) {
                final Long courseResId = Long.parseLong(string);
                final RepositoryEntry repoEntry = RepositoryManager.getInstance().lookupRepositoryEntry(
                        OresHelper.createOLATResourceableInstance(CourseModule.class, courseResId), false);
                if (repoEntry != null) {
                    // try to load course...
                    try {
                        // CourseFactory.loadCourse(courseResId);
                        // check whether the runstructure is there (faster then loading the whole course)
                        final File runstructure = new File(bcRoot + "/course/" + courseResId + "/runstructure.xml");
                        if (!runstructure.exists()) {
                            log.warn("Missing course structure file: " + runstructure.getAbsolutePath());
                        }
                        final File editstructure = new File(
                                bcRoot + "/course/" + courseResId + "/editortreemodel.xml");
                        if (!editstructure.exists()) {
                            log.warn("Missing course structure file: " + editstructure.getAbsolutePath());
                        }
                        final File courseconfig = new File(bcRoot + "/course/" + courseResId + "/CourseConfig.xml");
                        if (!courseconfig.exists()) {
                            log.warn("Missing course structure file: " + courseconfig.getAbsolutePath());
                        }
                    } catch (final Exception e) {
                        log.warn("Could not load course for resId: " + courseResId, e);
                    }
                } else {
                    log.warn("No repositoryEntry found for: " + courseResId);
                }

                if (counter > 0 && counter % 100 == 0) {
                    log.audit("Another 100 courses done");
                    DBFactory.getInstance().intermediateCommit();
                }
                counter++;
            }

            // now by database
            counter = 0;
            final List<RepositoryEntry> entries = RepositoryManager.getInstance()
                    .queryByType(CourseModule.ORES_TYPE_COURSE);
            for (final RepositoryEntry repositoryEntry : entries) {
                final Long courseResId = repositoryEntry.getOlatResource().getResourceableId();

                final File runstructure = new File(bcRoot + "/course/" + courseResId + "/runstructure.xml");
                if (!runstructure.exists()) {
                    log.warn("Course is in DB but not on Filesystem: Missing course structure file: "
                            + runstructure.getAbsolutePath());
                }
                final File editstructure = new File(bcRoot + "/course/" + courseResId + "/editortreemodel.xml");
                if (!editstructure.exists()) {
                    log.warn("Course is in DB but not on Filesystem: Missing course structure file: "
                            + editstructure.getAbsolutePath());
                }
                final File courseconfig = new File(bcRoot + "/course/" + courseResId + "/CourseConfig.xml");
                if (!courseconfig.exists()) {
                    log.warn("Course is in DB but not on Filesystem: Missing course structure file: "
                            + courseconfig.getAbsolutePath());
                }

                if (counter > 0 && counter % 100 == 0) {
                    log.audit("Another 100 courses done");
                    DBFactory.getInstance().intermediateCommit();
                }
                counter++;
            }

            uhd.setBooleanDataValue(TASK_CLEANUP_BROKEN_COURSES, false); // FIXME: set to true when done
            upgradeManager.setUpgradesHistory(uhd, VERSION);
        }

    }

    private void migrateNotifications(final UpgradeManager upgradeManager, final UpgradeHistoryData uhd) {
        if (!uhd.getBooleanDataValue(TASK_MIGRATE_NOTIFICATIONS)) {
            log.audit("+-----------------------------------------------------------------------------+");
            log.audit("+... Calculate the businesspath for the publishers (notifications)         ...+");
            log.audit("+-----------------------------------------------------------------------------+");

            int counter = 0;
            final NotificationsManager notificationMgr = NotificationsManager.getInstance();
            final List<Publisher> allPublishers = notificationMgr.getAllPublisher();
            if (log.isDebug()) {
                log.info("Found " + allPublishers.size() + " publishers to migrate.");
            }

            getNotificationUpgrades();

            for (final Publisher publisher : allPublishers) {
                final Publisher publisherToSave = upgrade(publisher);
                if (publisherToSave != null) {
                    try {
                        DBFactory.getInstance().updateObject(publisherToSave);
                    } catch (final ObjectDeletedException e) {
                        log.warn("Publisher was already deleted, no update possible! Publisher key: "
                                + publisherToSave.getKey());
                    } catch (final Exception e) {
                        log.warn("Publisher was already deleted, no update possible! Publisher key: "
                                + publisherToSave.getKey());
                    }
                    counter++;
                }
                if (counter > 0 && counter % 100 == 0) {
                    log.audit("Another 100 publishers done");
                    DBFactory.getInstance().intermediateCommit();
                }
            }

            DBFactory.getInstance().intermediateCommit();
            log.audit("**** Migrated " + counter + " publishers. ****");

            log.audit("+-----------------------------------------------------------------------------+");
            log.audit("+... Update the latest emailed date for all subscribers                       +");
            log.audit("+-----------------------------------------------------------------------------+");
            final DBQuery query = DBFactory.getInstance().createQuery("update " + Subscriber.class.getName()
                    + " subscriber set subscriber.latestEmailed=:latestDate");
            final Calendar cal = Calendar.getInstance();
            //
            // use the day of installing the release,
            // and set the time back to midnight instead of
            // going back one day, e.g. cal.add(Calendar.DAY_OF_MONTH, -1);
            //
            // 1) before release day, sending notifications the old way at 02:00:00 a.m.
            // 2) at release day, sending notifications the old way at 02:00:00 a.m.
            // .. Install the Release -> Upgrader sets latestEmail sent on subscribers to release day at 00:00:00
            // 3) day after release, sending notifications the new way at 02:00:00 a.m.
            //
            // with this procedure only the news are sent twice which were created between 00:00:00 and 02:00:00 of release day.
            //
            cal.set(Calendar.HOUR_OF_DAY, 0);
            cal.set(Calendar.MINUTE, 0);
            cal.set(Calendar.SECOND, 0);
            cal.set(Calendar.MILLISECOND, 0);
            query.setTimestamp("latestDate", cal.getTime());
            final int subCounter = query.executeUpdate(FlushMode.AUTO);

            DBFactory.getInstance().intermediateCommit();
            log.audit("**** Migrated " + subCounter + " subscribers. ****");

            uhd.setBooleanDataValue(TASK_MIGRATE_NOTIFICATIONS, true);
            upgradeManager.setUpgradesHistory(uhd, VERSION);
        }
    }

    private Publisher upgrade(final Publisher publisher) {
        if (publisher == null) {
            return null;
        }
        if (publisher.getData() != null && publisher.getData().startsWith("[")) {
            return null;
        }

        final String type = publisher.getType();
        if (notificationUpgrades == null) {
            log.error("No upgrader");
        }

        final NotificationsUpgrade upgrade = notificationUpgrades.get(type);
        if (upgrade == null) {
            log.error("No upgrader for publisher: " + publisher.getType());
            return null;
        }
        log.audit("upgrading..." + upgrade.getClass().getName());
        return upgrade.ugrade(publisher);
    }

    private void migrateMessages(final UpgradeManager upgradeManager, final UpgradeHistoryData uhd) {
        if (!uhd.getBooleanDataValue(TASK_MIGRATE_FORUMS_MESSAGES)) {
            log.audit("+-----------------------------------------------------------------------------+");
            log.audit("+... Calcualating word and character count in existing forum posts         ...+");
            log.audit("+-----------------------------------------------------------------------------+");

            int counter = 0;
            final ForumManager fMgr = ForumManager.getInstance();
            final List<Long> allForumKeys = fMgr.getAllForumKeys();
            if (log.isDebug()) {
                log.info("Found " + allForumKeys.size() + " forums to migrate.");
            }

            for (final Long forumKey : allForumKeys) {
                final List<Message> allMessages = fMgr.getMessagesByForumID(forumKey);
                for (final Message message : allMessages) {
                    try {
                        final String body = message.getBody();
                        final Locale locale = languageService.detectLocale(body);
                        final int characters = languageService.characterCount(body, locale);
                        message.setNumOfCharacters(characters);
                        final int words = languageService.wordCount(body, locale);
                        message.setNumOfWords(words);
                        counter++;

                        DBFactory.getInstance().updateObject(message);

                        if (counter > 0 && counter % 100 == 0) {
                            log.audit("Another 100 messages done");
                            DBFactory.getInstance().intermediateCommit();
                        }
                    } catch (final Exception e) {
                        log.error("Error during Migration: " + e, e);
                        DBFactory.getInstance().rollback();
                    }
                }
            }

            DBFactory.getInstance().intermediateCommit();
            log.audit("**** Migrated " + counter + " messages. ****");
            uhd.setBooleanDataValue(TASK_MIGRATE_FORUMS_MESSAGES, true);
            upgradeManager.setUpgradesHistory(uhd, VERSION);
        }
    }

    private void cleanupTmpUploadFiles(final UpgradeManager upgradeManager, final UpgradeHistoryData uhd) {
        if (!uhd.getBooleanDataValue(TASK_CLEANUP_TMP_UPLOAD_FILES_KEY)) {
            log.audit("+-----------------------------------------------------------------------------+");
            log.audit("+... Cleaning up old temporary upload files                                ...+");
            log.audit("+-----------------------------------------------------------------------------+");

            final File tempUploadDir = new File(WebappHelper.getUserDataRoot() + "/tmp/");
            long counter = 0;
            long mem = 0;
            if (tempUploadDir.exists() && tempUploadDir.isDirectory()) {
                // get all files that start with instanceID_NodeID_ followed by a number
                final FileFilter tmpUploadFileFilter = new RegexFileFilter(
                        WebappHelper.getInstanceId() + "_" + nodeId + "_[0-9]*");
                final File[] tmpUploadFiles = tempUploadDir.listFiles(tmpUploadFileFilter);
                for (final File file : tmpUploadFiles) {
                    if (file.isFile() && file.exists()) {
                        mem += file.length();
                        file.delete();
                        counter++;
                    }
                }
            }
            // ok, all done, commit done task to upgrade manager
            uhd.setBooleanDataValue(TASK_CLEANUP_TMP_UPLOAD_FILES_KEY, true);
            upgradeManager.setUpgradesHistory(uhd, VERSION);
            // some info output
            log.info("Deleted #" + counter + " temporary upload files that consumed a total of "
                    + StringHelper.formatMemory(mem) + " expensive diskspace. Pure happyness for your sysadmin.");
        }
    }

    private void migrateCourseLogFiles(final UpgradeManager upgradeManager, final UpgradeHistoryData uhd) {
        if (!uhd.getBooleanDataValue(TASK_MIGRATE_COURSE_LOG_FILES)) {
            log.audit("+-----------------------------------------------------------------------------+");
            log.audit("+... Migrate the Course Log Files                                          ...+");
            log.audit("+-----------------------------------------------------------------------------+");

            // doing an intermediate commit at the start to make sure we don't have any transaction open
            // further below we don't do anything with the database, it's raw file operations, hence
            // no further intermediatecommit is needed afterwards
            DBFactory.getInstance().intermediateCommit();

            final File globalOldCourseLogsDir = new File(deletionModule.getArchiveRootPath(),
                    OLD_COURSE_LOGS_DIRNAME);
            if (globalOldCourseLogsDir.exists() && !globalOldCourseLogsDir.isDirectory()) {
                log.error("**** !!!! Resource exists but is not a directory - cannot move course log files: "
                        + globalOldCourseLogsDir.getAbsolutePath());
                throw new IllegalStateException(
                        "Resource exists but is not a directory - cannot move course log files - owner/permission issue?: "
                                + globalOldCourseLogsDir.getAbsolutePath());
                // globalOldCourseLogsDir = null;
            } else if (!globalOldCourseLogsDir.exists() && !globalOldCourseLogsDir.mkdirs()) {
                log.error("**** !!!! Cannot create directory - cannot move course log files: "
                        + globalOldCourseLogsDir.getAbsolutePath());
                throw new IllegalStateException(
                        "Cannot create directory - cannot move course log files - owner/permission issue?: "
                                + globalOldCourseLogsDir.getAbsolutePath());
                // globalOldCourseLogsDir = null;
            }

            final File courseRootDir = new File(
                    FolderConfig.getCanonicalRoot() + File.separator + PersistingCourseImpl.COURSE_ROOT_DIR_NAME);

            final File[] dirs = courseRootDir.listFiles();
            int nonCourseDirs = 0;
            int migratedCourses = 0;
            int zipErrors = 0;
            int moveErrors = 0;
            int coursesWithoutLogsDir = 0;
            for (int i = 0; i < dirs.length; i++) {
                final File aDir = dirs[i];
                nonCourseDirs++;
                if (!aDir.isDirectory()) {
                    continue;
                }
                if (!aDir.getName().matches("[0123456789]*")) {
                    continue;
                }
                if (!aDir.getName().toLowerCase().equals(aDir.getName().toUpperCase())) {
                    // kind of superfluous check, but still...
                    continue;
                }
                nonCourseDirs--;
                final File courseLogDir = new File(aDir, LOGS_DIRNAME);
                if (!courseLogDir.isDirectory() || !courseLogDir.exists()) {
                    coursesWithoutLogsDir++;
                    continue;
                }

                final boolean zipSuccess = zipCourseLogFiles(courseLogDir);
                boolean moveSuccess = false;
                if (zipSuccess && globalOldCourseLogsDir != null) {
                    moveSuccess = moveInvisibleCourseLogFiles(courseLogDir, globalOldCourseLogsDir);
                }

                if (!zipSuccess) {
                    zipErrors++;
                }
                if (!moveSuccess) {
                    moveErrors++;
                }
                migratedCourses++;

                if (migratedCourses > 0 && migratedCourses % 100 == 0) {
                    log.audit("Another 100 course log files migrated, " + migratedCourses + " done. Total-Dirs: "
                            + dirs.length + ", Non-Course-Dirs: " + nonCourseDirs + ". Courses without logs dir: "
                            + coursesWithoutLogsDir + ". Errors: " + zipErrors + " zip errors, " + moveErrors
                            + " move errors, " + filesWithApacheConversionErrors_
                            + " apache-conversion errors. ****");
                }
            }

            log.audit("**** Migrated " + migratedCourses + " courses. Total-Dirs: " + dirs.length
                    + ", Non-Course-Dirs: " + nonCourseDirs + ". Courses without logs dir: " + coursesWithoutLogsDir
                    + ". Errors: " + zipErrors + " zip errors, " + moveErrors + " move errors, "
                    + filesWithApacheConversionErrors_ + " apache-conversion errors ****");
            uhd.setBooleanDataValue(TASK_MIGRATE_COURSE_LOG_FILES, true);
            upgradeManager.setUpgradesHistory(uhd, VERSION);
        }
    }

    public File createTempDirectory() {
        File temp = null;

        try {
            temp = File.createTempFile("temp_olat_migrate", Long.toString(System.nanoTime()));
        } catch (final IOException ioe) {
            log.error("**** !!!! Could not get temporary file");
        }

        if (!(temp.delete())) {
            log.error("**** !!!! Could not delete temp file: " + temp.getAbsolutePath());
        }

        if (!(temp.mkdir())) {
            log.error("**** !!!! Could not create temp directory: " + temp.getAbsolutePath());
        }

        return (temp);
    }

    private boolean zipCourseLogFiles(final File courseLogDir) {
        final File[] logFiles = courseLogDir.listFiles();
        final Set<String> toBeZippedFiles = new HashSet<String>();
        final Set<File> toBeDeletedFiles = new HashSet<File>();
        File readMe = null;
        for (int i = 0; i < logFiles.length; i++) {
            final File logFile = logFiles[i];
            final String logFileName = logFile.getName();
            if (logFileName.equals(FILENAME_ADMIN_LOG) && courseModule.isAdminLogVisibleForMigrationOnly()) {
                toBeZippedFiles.add(logFile.getName());
                toBeDeletedFiles.add(logFile);
            } else if (logFileName.equals(FILENAME_USER_LOG) && courseModule.isUserLogVisibleForMigrationOnly()) {
                toBeZippedFiles.add(logFile.getName());
                toBeDeletedFiles.add(logFile);
            } else if (logFileName.equals(FILENAME_STATISTIC_LOG)
                    && courseModule.isStatisticLogVisibleForMigrationOnly()) {
                toBeZippedFiles.add(logFile.getName());
                toBeDeletedFiles.add(logFile);
            } else if (logFileName.equals(README)) {
                readMe = logFile;
            }
        }

        if (readMe != null && toBeZippedFiles.size() > 0) {
            toBeZippedFiles.add(readMe.getName());
            toBeDeletedFiles.add(readMe);
        }

        final File courseFolder = new File(courseLogDir.getParentFile(), COURSEFOLDER);
        if (courseFolder.exists() && !courseFolder.isDirectory()) {
            log.error("**** !!!! Could not migrate course log files for " + courseLogDir.getParentFile().getName()
                    + " as there is a file called '" + COURSEFOLDER + " which was expected to be a directory: "
                    + courseFolder.getAbsolutePath());
            return false;
        }

        if (!courseFolder.exists()) {
            if (!courseFolder.mkdirs()) {
                log.error("**** !!!! Could not create directory " + courseFolder.getAbsolutePath());
                return false;
            }
        }

        final File oldCourseLogsDir = new File(courseFolder, OLD_COURSE_LOGS_DIRNAME);
        if (oldCourseLogsDir.exists()) {
            log.error("**** !!!! " + OLD_COURSE_LOGS_DIRNAME + " alreday existed! Not migrating course. Dir= "
                    + oldCourseLogsDir.getAbsolutePath());
            return false;
        }
        if (!oldCourseLogsDir.mkdirs()) {
            log.error("**** !!!! Could not create directory " + oldCourseLogsDir.getAbsolutePath());
            return false;
        }

        final File oldCourseLogsZip = new File(oldCourseLogsDir, OLD_COURSE_LOGS_ZIPFILENAME);
        if (!ZipUtil.zip(toBeZippedFiles, courseLogDir, oldCourseLogsZip, true)) {
            log.error("**** !!!! Could not zip course log files from " + courseLogDir + ", into "
                    + oldCourseLogsZip.getAbsolutePath());
            return false;
        }

        // now convert those files into apache log format
        final File tempDir = createTempDirectory();
        final Set<String> toBeApacheLoggedFiles = new HashSet<String>();
        for (final Iterator<File> it = toBeDeletedFiles.iterator(); it.hasNext();) {
            final File toBeApacheLoggedFile = it.next();
            if (toBeApacheLoggedFile.getName().equals(README)) {
                // ignore the readme file
                continue;
            }
            final File apacheLogFile = readSequence(toBeApacheLoggedFile, tempDir);
            toBeApacheLoggedFiles.add(apacheLogFile.getName());
        }
        final File oldCourseLogsInApacheFormatZip = new File(oldCourseLogsDir,
                OLD_COURSE_LOGS_IN_APACHE_FORMAT_ZIPFILENAME);
        if (!ZipUtil.zip(toBeApacheLoggedFiles, tempDir, oldCourseLogsInApacheFormatZip, true)) {
            log.error("**** !!!! Could not zip course log files (those in apache format) from " + tempDir
                    + ", into " + oldCourseLogsInApacheFormatZip.getAbsolutePath());
            if (!FileUtils.deleteDirsAndFiles(tempDir, true, true)) {
                tempDir.deleteOnExit();
            }
            return false;
        }
        if (!FileUtils.deleteDirsAndFiles(tempDir, true, true)) {
            tempDir.deleteOnExit();
        }

        // now delete those files
        for (final Iterator<File> it = toBeDeletedFiles.iterator(); it.hasNext();) {
            final File toBeDeletedFile = it.next();
            if (!toBeDeletedFile.delete()) {
                log.error("**** !!!! Could not delete file " + toBeDeletedFile.getAbsolutePath());
                return false;
            }
        }

        return true;
    }

    private boolean moveInvisibleCourseLogFiles(final File courseLogDir, final File globalOldCourseLogsDir) {
        File[] logFiles = courseLogDir.listFiles();
        final Set<File> toBeMovedFiles = new HashSet<File>();
        for (int i = 0; i < logFiles.length; i++) {
            final File logFile = logFiles[i];
            final String logFileName = logFile.getName();
            if (logFileName.equals(FILENAME_ADMIN_LOG) && !courseModule.isAdminLogVisibleForMigrationOnly()) {
                toBeMovedFiles.add(logFile);
            } else if (logFileName.equals(FILENAME_USER_LOG) && !courseModule.isUserLogVisibleForMigrationOnly()) {
                toBeMovedFiles.add(logFile);
            } else if (logFileName.equals(FILENAME_STATISTIC_LOG)
                    && !courseModule.isStatisticLogVisibleForMigrationOnly()) {
                toBeMovedFiles.add(logFile);
            }
        }

        final File concreteOldCourseLogsDir = new File(globalOldCourseLogsDir,
                courseLogDir.getParentFile().getName());
        if (concreteOldCourseLogsDir.exists() && !concreteOldCourseLogsDir.isDirectory()) {
            log.error("**** !!!! Resource exists but is not a directory: "
                    + concreteOldCourseLogsDir.getAbsolutePath());
            return false;
        } else if (!concreteOldCourseLogsDir.exists() && !concreteOldCourseLogsDir.mkdirs()) {
            log.error("**** !!!! Could not create directory:: " + concreteOldCourseLogsDir.getAbsolutePath());
            return false;
        }

        for (final Iterator<File> it = toBeMovedFiles.iterator(); it.hasNext();) {
            final File toBeMovedFile = it.next();
            final File targetFile = new File(concreteOldCourseLogsDir, toBeMovedFile.getName());
            if (!toBeMovedFile.renameTo(targetFile)) {
                log.error("**** !!!! Could not move file " + toBeMovedFile.getAbsolutePath() + " to "
                        + targetFile.getAbsolutePath());
                return false;
            }
        }

        logFiles = courseLogDir.listFiles();
        if (logFiles != null && logFiles.length > 0) {
            log.warn("**** !!!! Directory is not empty: " + courseLogDir.getAbsolutePath());
            return false;
        }

        if (!courseLogDir.delete()) {
            log.error("**** !!!! Could not delete directory: " + courseLogDir.getAbsolutePath());
            return false;
        }

        return true;
    }

    /** copied from 6.2.x version of CourseLogsArchiveManager and modified file handling to avoid going via VFS **/
    private File readSequence(final File leaf, final File outDir) {

        String line;
        final File resultingFile = new File(outDir, leaf.getName());

        BufferedReader br = null;
        FileOutputStream fos = null;
        BufferedWriter writer = null;
        boolean zeroErrors = true;
        try {
            br = new BufferedReader(new InputStreamReader(new FileInputStream(leaf)));
            fos = new FileOutputStream(resultingFile);
            writer = new BufferedWriter(new OutputStreamWriter(fos));
            while (null != (line = br.readLine())) {
                line = convertLine(line);
                // <MODIFIED FOR SAFETY>
                if (line.length() == 0) {
                    log.warn("**** !!!! Conversion failed with file: " + leaf);
                    zeroErrors = false;
                }
                // </MODIFIED FOR SAFETY>
                writer.append(line);
                writer.append("\r\n");
            }
        } catch (final IOException e) {
            log.error("**** !!!! Could not convert file to apache format: " + leaf);
            return null;
        } finally {
            if (!zeroErrors) {
                filesWithApacheConversionErrors_++;
            }
            if (br != null) {
                try {
                    br.close();
                } catch (final Exception e) {
                    // this empty catch is ok
                }
            }
            if (writer != null) {
                try {
                    writer.close();
                } catch (final Exception e) {
                    // this empty catch is ok
                }
            }
        }
        return resultingFile;
    }

    /** copied 1:1 from 6.2.x version of CourseLogsArchiveManager **/
    private String convertLine(final String line) {
        final StringBuilder sb = new StringBuilder();
        final String[] splitters = line.split("\t");

        // <MODIFIED FOR SAFETY>
        if (splitters.length < 5) {
            log.error("**** !!!! Could not convert line - fewer than 5 fields: " + line);
            return "";
        }
        // </MODIFIED FOR SAFETY>

        sb.append(splitters[2]);
        sb.append(" - ");
        sb.append(splitters[2]);

        final String timeStamp = splitters[0];
        sb.append(" [");
        sb.append(timeStamp.substring(8, 10)); // day
        sb.append("/");
        sb.append(getMonth(timeStamp.substring(5, 7))); // month
        sb.append("/");
        sb.append(timeStamp.substring(0, 4)); // year
        sb.append(":");
        sb.append(timeStamp.substring(11, 16));
        sb.append(" +0000] \"GET /");
        sb.append(splitters[3].trim());
        sb.append("_");
        sb.append(splitters[4].replaceAll(" ", "_"));
        if (splitters.length > 5) {
            sb.append("_");
            sb.append(splitters[5].trim());
        }
        if (splitters.length > 6) {
            sb.append("_");
            sb.append(splitters[6].trim());
        }

        sb.append(" HTTP/1.0\" 200 100");
        return sb.toString();
    }

    /** copied 1:1 from 6.2.x version of CourseLogsArchiveManager **/
    private String getMonth(String num) {
        if (num.startsWith("0")) {
            num = num.substring(1);
        }
        final int i = Integer.parseInt(num);
        final String[] months = new String[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct",
                "Nov", "Dec" };
        return months[i - 1];
    }

    public String getVersion() {
        return VERSION;
    }

    public Map<String, NotificationsUpgrade> getNotificationUpgrades() {
        if (notificationUpgrades == null) {
            synchronized (lockObject) {
                if (notificationUpgrades == null) { // check again in synchronized-block, only one may create list
                    notificationUpgrades = new HashMap<String, NotificationsUpgrade>();
                    final Map<String, Object> notificationUpgradeMap = CoreSpringFactory
                            .getBeansOfType(CoreBeanTypes.notificationsUpgrade);
                    final Collection<Object> notificationUpgradeValues = notificationUpgradeMap.values();
                    for (final Object object : notificationUpgradeValues) {
                        final NotificationsUpgrade notificationsUpgrade = (NotificationsUpgrade) object;
                        log.debug("initNotificationUpgrades notificationsUpgrade=" + notificationsUpgrade);
                        notificationUpgrades.put(notificationsUpgrade.getType(), notificationsUpgrade);
                    }
                }
            }
        }
        return notificationUpgrades;
    }

}