org.openvpms.web.jobs.docload.DocumentLoaderJob.java Source code

Java tutorial

Introduction

Here is the source code for org.openvpms.web.jobs.docload.DocumentLoaderJob.java

Source

/*
 * Version: 1.0
 *
 * The contents of this file are subject to the OpenVPMS License Version
 * 1.0 (the 'License'); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.openvpms.org/license/
 *
 * Software distributed under the License is distributed on an 'AS IS' basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * Copyright 2015 (C) OpenVPMS Ltd. All Rights Reserved.
 */

package org.openvpms.web.jobs.docload;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openvpms.archetype.rules.workflow.MessageArchetypes;
import org.openvpms.archetype.rules.workflow.SystemMessageReason;
import org.openvpms.component.business.domain.im.act.Act;
import org.openvpms.component.business.domain.im.archetype.descriptor.ArchetypeDescriptor;
import org.openvpms.component.business.domain.im.archetype.descriptor.NodeDescriptor;
import org.openvpms.component.business.domain.im.common.Entity;
import org.openvpms.component.business.domain.im.security.User;
import org.openvpms.component.business.service.archetype.IArchetypeService;
import org.openvpms.component.business.service.archetype.helper.ActBean;
import org.openvpms.component.business.service.archetype.helper.DescriptorHelper;
import org.openvpms.component.business.service.archetype.helper.EntityBean;
import org.openvpms.component.business.service.archetype.helper.IMObjectBean;
import org.openvpms.component.business.service.archetype.rule.IArchetypeRuleService;
import org.openvpms.etl.tools.doc.DefaultLoaderListener;
import org.openvpms.etl.tools.doc.IdLoader;
import org.openvpms.etl.tools.doc.LoaderListener;
import org.openvpms.etl.tools.doc.LoggingLoaderListener;
import org.openvpms.web.resource.i18n.Messages;
import org.openvpms.web.workspace.workflow.messaging.MessageHelper;
import org.quartz.InterruptableJob;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.Scheduler;
import org.quartz.StatefulJob;
import org.quartz.Trigger;
import org.quartz.UnableToInterruptJobException;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.util.StringUtils;

import java.io.File;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

/**
 * A job for loading documents using the {@link IdLoader}.
 *
 * @author Tim Anderson
 */
public class DocumentLoaderJob implements InterruptableJob, StatefulJob {

    /**
     * The job configuration.
     */
    private final Entity configuration;

    /**
     * The archetype service
     */
    private final IArchetypeService service;

    /**
     * The transaction manager.
     */
    private final PlatformTransactionManager transactionManager;

    /**
     * The maximum subject length.
     */
    private final int subjectLength;

    /**
     * The maximum message length.
     */
    private final int messageLength;

    /**
     * The logger.
     */
    private static final Log log = LogFactory.getLog(DocumentLoaderJob.class);

    /**
     * Determines if loading should stop.
     */
    private volatile boolean stop;

    /**
     * Constructs a {@link DocumentLoaderJob}.
     *
     * @param configuration      the configuration
     * @param service            the archetype service
     * @param transactionManager the transaction manager
     */
    public DocumentLoaderJob(Entity configuration, IArchetypeRuleService service,
            PlatformTransactionManager transactionManager) {

        this.configuration = configuration;
        this.service = service;
        this.transactionManager = transactionManager;
        ArchetypeDescriptor descriptor = DescriptorHelper.getArchetypeDescriptor(MessageArchetypes.SYSTEM, service);
        subjectLength = getMaxLength(descriptor, "description");
        messageLength = getMaxLength(descriptor, "message");
    }

    /**
     * Called by the {@link Scheduler} when a {@link Trigger} fires that is associated with the {@code Job}.
     *
     * @throws JobExecutionException if there is an exception while executing the job.
     */
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        Listener listener = null;
        try {
            stop = false;
            IMObjectBean bean = new IMObjectBean(configuration, service);
            File source = getDir(bean.getString("sourceDir"));
            if (source == null || !source.exists()) {
                throw new IllegalStateException("Invalid source directory: " + source);
            }
            File target = getDir(bean.getString("targetDir"));
            if (target == null || !target.exists()) {
                throw new IllegalStateException("Invalid destination directory: " + target);
            }
            String idPattern = bean.getString("idPattern");
            boolean overwrite = bean.getBoolean("overwrite");
            boolean recurse = bean.getBoolean("recurse");
            String[] types = bean.getString("archetypes", "").split(",");
            types = StringUtils.trimArrayElements(types);
            boolean logLoad = bean.getBoolean("log");
            boolean stopOnError = bean.getBoolean("stopOnError");

            IdLoader loader = new IdLoader(source, types, service, transactionManager, recurse, overwrite,
                    Pattern.compile(idPattern));
            LoaderListener delegate = logLoad ? new LoggingLoaderListener(log, target)
                    : new DefaultLoaderListener(target);
            listener = new Listener(delegate);
            loader.setListener(listener);

            while (!stop && loader.hasNext()) {
                if (!loader.loadNext() && stopOnError) {
                    break;
                }
            }
            complete(listener, null);
        } catch (Throwable exception) {
            log.error(exception, exception);
            complete(listener, exception);
        }
    }

    /**
     * <p/>
     * Called by the {@link Scheduler} when a user interrupts the {@code Job}.
     *
     * @throws UnableToInterruptJobException if there is an exception while interrupting the job.
     */
    @Override
    public void interrupt() throws UnableToInterruptJobException {
        stop = true;
    }

    /**
     * Invoked on completion of a jobSends a message notifying the registered users of completion or failure of the job.
     *
     * @param listener  the loader listener
     * @param exception the exception, if the job failed, otherwise {@code null}
     */
    private void complete(Listener listener, Throwable exception) {
        if ((listener != null && (listener.getErrors() != 0 || listener.getLoaded() != 0)) || exception != null) {
            EntityBean bean = new EntityBean(configuration, service);
            Set<User> users = MessageHelper.getUsers(bean.getNodeTargetEntities("notify"));
            if (!users.isEmpty()) {
                notifyUsers(listener, exception, users);
            }
        }
    }

    /**
     * Notifies users of completion or failure of the job.
     *
     * @param listener  the loader listener
     * @param exception the exception, if the job failed, otherwise {@code null}
     * @param users     the users to notify
     */
    private void notifyUsers(Listener listener, Throwable exception, Set<User> users) {
        String subject;
        String reason;
        StringBuilder text = new StringBuilder();
        if (exception != null) {
            reason = SystemMessageReason.ERROR;
            subject = Messages.format("docload.subject.exception", configuration.getName());
            text.append(Messages.format("docload.exception", exception.getMessage()));
        } else {
            int loaded = listener.getLoaded();
            int errors = listener.getErrors();
            if (errors != 0) {
                reason = SystemMessageReason.ERROR;
                subject = Messages.format("docload.subject.errors", configuration.getName(), errors);
            } else {
                reason = SystemMessageReason.COMPLETED;
                subject = Messages.format("docload.subject.success", configuration.getName(), loaded);
            }
        }
        if (listener != null) {
            if (!listener.errors.isEmpty()) {
                if (text.length() != 0) {
                    text.append("\n\n");
                }
                text.append(Messages.get("docload.error"));
                text.append("\n");
                for (Map.Entry<File, String> entry : listener.errors.entrySet()) {
                    text.append(Messages.format("docload.error.item", entry.getKey(), entry.getValue()));
                    text.append("\n");
                }
            }
            if (!listener.missingAct.isEmpty()) {
                if (text.length() != 0) {
                    text.append("\n\n");
                }
                text.append(Messages.get("docload.missingAct"));
                text.append("\n");
                for (Map.Entry<File, Long> entry : listener.missingAct.entrySet()) {
                    text.append(Messages.format("docload.missingAct.item", entry.getValue(), entry.getKey()));
                    text.append("\n");
                }
            }
            if (!listener.alreadyLoaded.isEmpty()) {
                if (text.length() != 0) {
                    text.append("\n\n");
                }
                text.append(Messages.get("docload.alreadyLoaded"));
                text.append("\n");
                for (Map.Entry<File, Long> entry : listener.alreadyLoaded.entrySet()) {
                    text.append(Messages.format("docload.alreadyLoaded.item", entry.getValue(), entry.getKey()));
                    text.append("\n");
                }
            }
        }
        subject = truncate(subject, subjectLength);
        String message = truncate(text.toString(), messageLength);
        for (User user : users) {
            send(user, subject, reason, message);
        }
    }

    /**
     * Sends a message to a user.
     *
     * @param user    the user to send to
     * @param subject the subject
     * @param reason  the reason
     * @param text    the message text
     */
    private void send(User user, String subject, String reason, String text) {
        Act act = (Act) service.create(MessageArchetypes.SYSTEM);
        ActBean message = new ActBean(act);
        message.addNodeParticipation("to", user);
        message.setValue("reason", reason);
        message.setValue("description", subject);
        message.setValue("message", text);
        message.save();
    }

    /**
     * Helper to determine the maximum length of a node.
     *
     * @param archetype the archetype descriptor
     * @param node      the node name
     * @return the maximum length of the node
     */
    private int getMaxLength(ArchetypeDescriptor archetype, String node) {
        int result = NodeDescriptor.DEFAULT_MAX_LENGTH;
        if (archetype != null) {
            NodeDescriptor descriptor = archetype.getNodeDescriptor(node);
            if (descriptor != null) {
                result = descriptor.getMaxLength();
            }
        }
        return result;
    }

    /**
     * Helper to return a file given a path.
     *
     * @param path the path. May be {@code null}
     * @return the file. May be {@code null}
     */
    private File getDir(String path) {
        return path != null ? new File(path) : null;
    }

    /**
     * Helper to truncate a string if it exceeds a maximum length.
     *
     * @param value     the value to truncate
     * @param maxLength the maximum length
     * @return the new value
     */
    private String truncate(String value, int maxLength) {
        return value != null && value.length() > maxLength ? value.substring(0, maxLength) : value;
    }

    private class Listener extends DelegatingLoaderListener {

        private LinkedHashMap<File, Long> alreadyLoaded = new LinkedHashMap<File, Long>();

        private LinkedHashMap<File, Long> missingAct = new LinkedHashMap<File, Long>();

        private LinkedHashMap<File, String> errors = new LinkedHashMap<File, String>();

        /**
         * Constructs a {@link Listener}.
         *
         * @param listener the listener to delegate to
         */
        public Listener(org.openvpms.etl.tools.doc.LoaderListener listener) {
            super(listener);
        }

        /**
         * Notifies that a file couldn't be loaded as it or another file had already been processed.
         *
         * @param file the file
         * @param id   the corresponding act identifier
         */
        @Override
        public void alreadyLoaded(File file, long id) {
            super.alreadyLoaded(file, id);
            alreadyLoaded.put(file, id);
        }

        /**
         * Notifies that a file couldn't be loaded as there was no corresponding act.
         *
         * @param file the file
         * @param id   the corresponding act identifier
         */
        @Override
        public void missingAct(File file, long id) {
            super.missingAct(file, id);
            missingAct.put(file, id);
        }

        /**
         * Notifies that a file couldn't be loaded due to error.
         *
         * @param file      the file
         * @param exception the error
         */
        @Override
        public void error(File file, Throwable exception) {
            super.error(file, exception);
            errors.put(file, exception.getMessage());
        }

    }

}