org.entcore.archive.services.impl.FileSystemExportService.java Source code

Java tutorial

Introduction

Here is the source code for org.entcore.archive.services.impl.FileSystemExportService.java

Source

/* Copyright  "Open Digital Education", 2014
 *
 * This program is published by "Open Digital Education".
 * You must indicate the name of the software and the company in any production /contribution
 * using the software and indicate on the home page of the software industry in question,
 * "powered by Open Digital Education" with a reference to the website: https://opendigitaleducation.com/.
 *
 * This program is free software, licensed under the terms of the GNU Affero General Public License
 * as published by the Free Software Foundation, version 3 of the License.
 *
 * You can redistribute this application and/or modify it since you respect the terms of the GNU Affero General Public License.
 * If you modify the source code and then use this modified source code in your creation, you must make available the source code of your modifications.
 *
 * You should have received a copy of the GNU Affero General Public License along with the software.
 * If not, please see : <http://www.gnu.org/licenses/>. Full compliance requires reading the terms of this license and following its directives.
    
 *
 */

package org.entcore.archive.services.impl;

import fr.wseduc.mongodb.MongoDb;
import fr.wseduc.webutils.Either;
import fr.wseduc.webutils.email.EmailSender;
import fr.wseduc.webutils.http.Renders;
import io.vertx.core.eventbus.DeliveryOptions;
import org.entcore.archive.Archive;
import org.entcore.archive.services.ExportService;
import org.entcore.archive.utils.User;
import org.entcore.common.http.request.JsonHttpServerRequest;
import org.entcore.common.neo4j.Neo4j;
import org.entcore.common.notification.TimelineHelper;
import org.entcore.common.storage.Storage;
import org.entcore.common.user.UserInfos;
import org.entcore.common.utils.Zip;
import io.vertx.core.AsyncResult;
import io.vertx.core.Handler;
import io.vertx.core.eventbus.EventBus;
import io.vertx.core.eventbus.Message;
import io.vertx.core.file.FileSystem;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.core.logging.Logger;
import io.vertx.core.logging.LoggerFactory;

import java.io.File;
import java.util.*;
import java.util.zip.Deflater;

import static fr.wseduc.webutils.Utils.handlerToAsyncHandler;

public class FileSystemExportService implements ExportService {

    private final FileSystem fs;
    private final EventBus eb;
    private final String exportPath;
    private final Set<String> expectedExports;
    private final EmailSender notification;
    private final Storage storage;
    private static final Logger log = LoggerFactory.getLogger(FileSystemExportService.class);
    private final Map<String, Long> userExportInProgress;
    private final TimelineHelper timeline;

    public FileSystemExportService(FileSystem fs, EventBus eb, String exportPath, Set<String> expectedExports,
            EmailSender notification, Storage storage, Map<String, Long> userExportInProgress,
            TimelineHelper timeline) {
        this.fs = fs;
        this.eb = eb;
        this.exportPath = exportPath;
        this.expectedExports = expectedExports;
        this.notification = notification;
        this.storage = storage;
        this.userExportInProgress = userExportInProgress;
        this.timeline = timeline;
    }

    @Override
    public void export(final UserInfos user, final String locale, final HttpServerRequest request,
            final Handler<Either<String, String>> handler) {
        userExportExists(user, new Handler<Boolean>() {
            @Override
            public void handle(Boolean event) {
                if (Boolean.FALSE.equals(event)) {
                    long now = System.currentTimeMillis();
                    final String exportId = now + "_" + user.getUserId();
                    userExportInProgress.put(user.getUserId(), now);
                    final String exportDirectory = exportPath + File.separator + exportId;
                    fs.mkdirs(exportDirectory, new Handler<AsyncResult<Void>>() {
                        @Override
                        public void handle(AsyncResult<Void> event) {
                            if (event.succeeded()) {
                                final Set<String> g = (user.getGroupsIds() != null)
                                        ? new HashSet<>(user.getGroupsIds())
                                        : new HashSet<String>();
                                User.getOldGroups(user.getUserId(), new Handler<JsonArray>() {
                                    @Override
                                    public void handle(JsonArray objects) {
                                        g.addAll(objects.getList());
                                        JsonObject j = new JsonObject().put("action", "export")
                                                .put("exportId", exportId).put("userId", user.getUserId())
                                                .put("groups",
                                                        new fr.wseduc.webutils.collections.JsonArray(
                                                                new ArrayList<>(g)))
                                                .put("path", exportDirectory).put("locale", locale)
                                                .put("host", Renders.getScheme(request) + "://"
                                                        + request.headers().get("Host"));
                                        eb.publish("user.repository", j);
                                        handler.handle(new Either.Right<String, String>(exportId));
                                    }
                                });
                            } else {
                                log.error("Create export directory error.", event.cause());
                                handler.handle(new Either.Left<String, String>("export.directory.create.error"));
                            }
                        }
                    });
                } else {
                    handler.handle(new Either.Left<String, String>("export.exists"));
                }
            }
        });
    }

    @Override
    public void userExportExists(UserInfos user, final Handler<Boolean> handler) {
        handler.handle(userExportInProgress.containsKey(user.getUserId()));
    }

    @Override
    public boolean userExportExists(String exportId) {
        return userExportInProgress.containsKey(getUserId(exportId));
    }

    @Override
    public void waitingExport(String exportId, final Handler<Boolean> handler) {
        Long v = userExportInProgress.get(getUserId(exportId));
        handler.handle(v != null && v > 0);
    }

    @Override
    public void exportPath(String exportId, final Handler<Either<String, String>> handler) {
        final String path = exportPath + File.separator + exportId + ".zip";
        fs.exists(path, new Handler<AsyncResult<Boolean>>() {
            @Override
            public void handle(AsyncResult<Boolean> event) {
                if (event.succeeded()) {
                    if (Boolean.TRUE.equals(event.result())) {
                        handler.handle(new Either.Right<String, String>(path));
                    } else {
                        handler.handle(new Either.Right<String, String>(null));
                    }
                } else {
                    log.error("Export exists error.", event.cause());
                    handler.handle(new Either.Left<String, String>("get.export.path.error"));
                }
            }
        });
    }

    @Override
    public void exported(final String exportId, String status, final String locale, final String host) {
        log.debug("Exported method");
        if (exportId == null) {
            log.error("Export receive event without exportId ");
            return;
        }
        final String exportDirectory = exportPath + File.separator + exportId;
        if (!"ok".equals(status)) {
            log.error("Error in export " + exportId);
            JsonObject j = new JsonObject().put("status", "error").put("message", "export.error");
            eb.publish("export." + exportId, j);
            fs.deleteRecursive(exportDirectory, true, new Handler<AsyncResult<Void>>() {
                @Override
                public void handle(AsyncResult<Void> event) {
                    if (event.failed()) {
                        log.error("Error deleting directory : " + exportDirectory, event.cause());
                    }
                }
            });
            userExportInProgress.remove(getUserId(exportId));
            if (notification != null) {
                sendExportEmail(exportId, locale, status, host);
            }
            return;
        }

        log.debug("Read export directory");
        fs.readDir(exportDirectory, new Handler<AsyncResult<List<String>>>() {
            @Override
            public void handle(AsyncResult<List<String>> event) {
                if (event.succeeded()) {
                    if (log.isDebugEnabled()) {
                        log.debug("read export directory : ok - Result length : " + event.result().size()
                                + ", expected " + expectedExports.size());
                    }
                    if (event.result().size() == expectedExports.size()) {
                        Zip.getInstance().zipFolder(exportDirectory, exportDirectory + ".zip", true,
                                Deflater.NO_COMPRESSION, new Handler<Message<JsonObject>>() {
                                    @Override
                                    public void handle(final Message<JsonObject> event) {
                                        if (!"ok".equals(event.body().getString("status"))) {
                                            log.error("Zip export " + exportId + " error : "
                                                    + event.body().getString("message"));
                                            event.body().put("message", "zip.export.error");
                                            userExportInProgress.remove(getUserId(exportId));
                                            fs.deleteRecursive(exportDirectory, true,
                                                    new Handler<AsyncResult<Void>>() {
                                                        @Override
                                                        public void handle(AsyncResult<Void> event) {
                                                            if (event.failed()) {
                                                                log.error("Error deleting directory : "
                                                                        + exportDirectory, event.cause());
                                                            }
                                                        }
                                                    });
                                            publish(event);
                                        } else {
                                            storeZip(event);
                                        }
                                    }

                                    private void storeZip(final Message<JsonObject> event) {
                                        storage.writeFsFile(exportId, exportDirectory + ".zip",
                                                new Handler<JsonObject>() {
                                                    @Override
                                                    public void handle(JsonObject res) {
                                                        if (!"ok".equals(res.getString("status"))) {
                                                            log.error("Zip storage " + exportId + " error : "
                                                                    + res.getString("message"));
                                                            event.body().put("message", "zip.saving.error");
                                                            userExportInProgress.remove(getUserId(exportId));
                                                            publish(event);
                                                        } else {
                                                            userExportInProgress.put(getUserId(exportId), -1l);
                                                            MongoDb.getInstance().save(Archive.ARCHIVES,
                                                                    new JsonObject().put("file_id", exportId)
                                                                            .put("date", MongoDb.now()),
                                                                    new Handler<Message<JsonObject>>() {
                                                                        @Override
                                                                        public void handle(
                                                                                Message<JsonObject> res) {
                                                                            publish(event);
                                                                        }
                                                                    });
                                                        }
                                                        deleteTempZip(exportId);
                                                    }
                                                });
                                    }

                                    public void deleteTempZip(final String exportId) {
                                        final String path = exportPath + File.separator + exportId + ".zip";
                                        fs.delete(path, new Handler<AsyncResult<Void>>() {
                                            @Override
                                            public void handle(AsyncResult<Void> event) {
                                                if (event.failed()) {
                                                    log.error("Error deleting temp zip export " + exportId,
                                                            event.cause());
                                                }
                                            }
                                        });
                                    }

                                    private void publish(final Message<JsonObject> event) {
                                        final String address = "export." + exportId;
                                        eb.send(address, event.body(), new DeliveryOptions().setSendTimeout(5000l),
                                                new Handler<AsyncResult<Message<JsonObject>>>() {

                                                    @Override
                                                    public void handle(AsyncResult<Message<JsonObject>> res) {
                                                        if (!res.succeeded() && userExportExists(exportId)
                                                                && !downloadIsInProgress(exportId)) {
                                                            if (notification != null) {
                                                                sendExportEmail(exportId, locale,
                                                                        event.body().getString("status"), host);
                                                            } else {
                                                                notifyOnTimeline(exportId, locale,
                                                                        event.body().getString("status"));
                                                            }
                                                        }
                                                    }
                                                });

                                    }
                                });
                    }
                } else {
                    log.error("Error listing export directory " + exportId, event.cause());
                }
            }
        });
    }

    @Override
    public void deleteExport(final String exportId) {
        storage.removeFile(exportId, new Handler<JsonObject>() {
            @Override
            public void handle(JsonObject event) {
                if (!"ok".equals(event.getString("status"))) {
                    log.error("Error deleting export " + exportId + ". - " + event.getString("message"));
                }
            }
        });
        MongoDb.getInstance().delete(Archive.ARCHIVES, new JsonObject().put("file_id", exportId));
        String userId = getUserId(exportId);
        userExportInProgress.remove(userId);
    }

    @Override
    public void setDownloadInProgress(String exportId) {
        String userId = getUserId(exportId);
        if (userExportInProgress.containsKey(userId)) {
            userExportInProgress.put(userId, -2l);
        }
    }

    @Override
    public boolean downloadIsInProgress(String exportId) {
        Long v = userExportInProgress.get(getUserId(exportId));
        return v != null && v == -2l;
    }

    private String getUserId(String exportId) {
        return exportId.substring(exportId.indexOf('_') + 1);
    }

    private void notifyOnTimeline(String exportId, String locale, String status) {
        final String userId = getUserId(exportId);
        List<String> recipients = new ArrayList<>();
        recipients.add(userId);
        final JsonObject params = new JsonObject().put("resourceUri", "/archive/export/" + exportId)
                .put("resourceName", exportId + ".zip");

        timeline.notifyTimeline(
                new JsonHttpServerRequest(
                        new JsonObject().put("headers", new JsonObject().put("Accept-Language", locale))),
                "archive.archives" + "_" + status, null, recipients, params);
    }

    private void sendExportEmail(final String exportId, final String locale, final String status,
            final String host) {
        final String userId = getUserId(exportId);
        String query = "MATCH (u:User {id : {userId}}) RETURN u.email as email ";
        JsonObject params = new JsonObject().put("userId", userId);
        Neo4j.getInstance().execute(query, params, new Handler<Message<JsonObject>>() {
            @Override
            public void handle(Message<JsonObject> event) {
                JsonArray res = event.body().getJsonArray("result");
                if ("ok".equals(event.body().getString("status")) && res != null && res.size() == 1) {
                    JsonObject e = res.getJsonObject(0);
                    String email = e.getString("email");
                    if (email != null && !email.trim().isEmpty()) {
                        HttpServerRequest r = new JsonHttpServerRequest(
                                new JsonObject().put("headers", new JsonObject().put("Accept-Language", locale)));
                        String subject, template;
                        JsonObject p = new JsonObject();
                        if ("ok".equals(status)) {
                            subject = "email.export.ok";
                            template = "email/export.ok.html";
                            p.put("download", host + "/archive/export/" + exportId);
                            if (log.isDebugEnabled()) {
                                log.debug(host + "/archive/export/" + exportId);
                            }
                        } else {
                            subject = "email.export.ko";
                            template = "email/export.ko.html";
                        }
                        notification.sendEmail(r, email, null, null, subject, template, p, true,
                                handlerToAsyncHandler(new Handler<Message<JsonObject>>() {
                                    @Override
                                    public void handle(Message<JsonObject> event) {
                                        if (event == null || !"ok".equals(event.body().getString("status"))) {
                                            log.error("Error sending export email for user " + userId);
                                        }
                                    }
                                }));
                    } else {
                        log.info("User " + userId + " hasn't email.");
                    }
                } else if (res != null) {
                    log.warn("User " + userId + " not found.");
                } else {
                    log.error("Error finding user " + userId + " email : " + event.body().getString("message"));
                }
            }
        });
    }

}