/*******************************************************************************
 * This file is part of GECAMed.
 * 
 * GECAMed is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License (L-GPL) as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * GECAMed 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 Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License (L-GPL)
 * along with GECAMed.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * GECAMed is Copyrighted by the Centre de Recherche Public Henri Tudor (http://www.tudor.lu)
 * (c) CRP Henri Tudor, Luxembourg, 2008
 *******************************************************************************/
package lu.tudor.santec.gecamed.backup.ejb.session.beans;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.sql.Connection;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import javax.annotation.Resource;
import javax.ejb.EJB;
import javax.ejb.Remote;
import javax.ejb.SessionContext;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.jms.TextMessage;
import javax.jms.TopicPublisher;
import javax.jms.TopicSession;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.sql.DataSource;

import lu.tudor.santec.gecamed.backup.ejb.entity.beans.BackupUser;
import lu.tudor.santec.gecamed.backup.ejb.mdb.beans.BackupSchedulerBean;
import lu.tudor.santec.gecamed.backup.ejb.session.interfaces.BackupManagerInterface;
import lu.tudor.santec.gecamed.backup.ejb.session.interfaces.FileInfo;
import lu.tudor.santec.gecamed.core.ejb.entity.beans.log.Log;
import lu.tudor.santec.gecamed.core.ejb.entity.beans.log.LogType;
import lu.tudor.santec.gecamed.core.ejb.session.interfaces.LogManager;
import lu.tudor.santec.gecamed.core.ejb.session.interfaces.MessageSenderInterface;
import lu.tudor.santec.gecamed.core.utils.FileUtils;
import lu.tudor.santec.gecamed.core.utils.ManagerFactory;
import lu.tudor.santec.gecamed.core.utils.ServerConfig;
import lu.tudor.santec.gecamed.usermanagement.ejb.entity.beans.UserMessage;
import lu.tudor.santec.gecamed.usermanagement.ejb.session.beans.UserMessageManager;
import lu.tudor.santec.gecamed.usermanagement.ejb.session.interfaces.UserMessageInterface;

import org.apache.commons.codec.binary.Base64;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;


@Remote({ BackupManagerInterface.class })
@Stateless
//@SecurityDomain("gecamedLoginDS")
public class BackupManagerBean implements BackupManagerInterface
{
	
	@Resource
	SessionContext			sessionContext;
	
	@PersistenceContext(unitName = "gecam")
	protected EntityManager	em;
	
	@EJB
	LogManager				logManager;
	
	@EJB
	MessageSenderInterface  messageSender;
	
	/**
	 * static logger for this class
	 */
	private static Logger	logger				= Logger.getLogger(BackupManagerBean.class.getName());
	
	public static String	BACKUP_SCRIPT		= ServerConfig.getProperty(ServerConfig.DB_BACKUP_SCRIPT);
	public static String	BACKUP_FOLDER		= ServerConfig.getProperty(ServerConfig.DB_BACKUP_DIR);
	public static String	FILE_DIR			= ServerConfig.getProperty(ServerConfig.FILE_BASE_DIR);
	public static String	TEMPLATE_DIR		= ServerConfig.getProperty(ServerConfig.TEMPLATE_DIR);
	public static String	DICOM_DIR			= ServerConfig.getProperty(ServerConfig.DICOM_DIR);
	
	public static String	JBOSS_DEPLOY_DIR	= System.getProperty("jboss.server.home.dir") + File.separator + "deploy";
	
	private static String	dbUser				= ServerConfig.getProperty(ServerConfig.DB_USERNAME);
	private static String	dbPass				= ServerConfig.getProperty(ServerConfig.DB_PASSWORD);
	private static String	dbName				= "gecamed_1_5_0";
	
	private static DateFormat	dateFormatter	= new SimpleDateFormat("yyyy-MM-dd hh:mm");

	private static Map<String, FileFilter> filters = new HashMap<String, FileFilter>();
	
	private DateFormat		fileDateFormat		= new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss");
	
	protected StringBuffer	output = new StringBuffer();
	
	protected boolean		error;
	
	
	/* (non-Javadoc)
	 * @see lu.tudor.santec.gecamed.backup.ejb.session.interfaces.BackupManagerInterface#checkPassword(java.lang.String, java.lang.String)
	 */
	public boolean checkPassword(String login, String password)
	{
		try
		{
			password = getEncodedHash(password);
			BackupUser user = (BackupUser) em.createNamedQuery(BackupUser.FIND_USER_BY_LOGIN).setParameter("login", login).getSingleResult();
			if (user.getPasswordhash().equals(password))
			{
				return true;
			}
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
		return false;
	}
	
	
	/* (non-Javadoc)
	 * @see lu.tudor.santec.gecamed.backup.ejb.session.interfaces.BackupManagerInterface#doBackup()
	 *
	 */
	@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
	public void doBackup(long timestamp) throws Exception
	{
		
		long start = System.currentTimeMillis();
		logger.info("Starting database backup");
		Date backupDate = new Date(timestamp);
		
		// retrieve DB name
		Context ic = new InitialContext();
		DataSource ds = (DataSource) ic.lookup("java:/gecamdb");
		Connection c = ds.getConnection();
		String[] dbConnectionParts = c.getMetaData().getURL().split("/");
		dbName = dbConnectionParts[dbConnectionParts.length - 1];
		
		String host	= "localhost";
		String port	= "5432";
		try
		{
			dbConnectionParts = dbConnectionParts[2].split(":");
			host = dbConnectionParts[0];
			if (dbConnectionParts.length > 1)
				port = dbConnectionParts[1];
		}
		catch (Exception e)
		{
			logger.warn("Couldn't read datbase port and / or host. Taken defaults: "+host+":"+port);
		}
		
		// retrieve Backup File
		File backupFile = new File(BACKUP_FOLDER + File.separator + dbName + "_" + fileDateFormat.format(backupDate) + "." + BACKUP_DB_EXTENSION);
		backupFile.getParentFile().mkdirs();
		
		String[] cmdArr = { BACKUP_SCRIPT, backupFile.getAbsolutePath(), dbUser, dbPass, dbName, port, host };
		
		//		// Debugging
		//		StringBuffer sb = new StringBuffer();
		//		for (int i = 0; i < cmdArr.length; i++) {
		//			sb.append(cmdArr[i] + " ");			
		//		}
		//		System.out.println(sb.toString());
		
		Runtime rt = Runtime.getRuntime();
		final Process p = rt.exec(cmdArr);
		readProcessOutput(p);
		p.waitFor();
		//		int retVal = p.exitValue();
		
		
		
		if (error || !backupFile.canRead() || backupFile.length() < 4000)
		{
			backupFile.delete();
			logger.warn("Error doing database backup " + output.toString());
			throw new Exception(output.toString());
		} else {
			logger.info("Database Backup output:\n" + output.toString());			
		}
		backupFile.setLastModified(backupDate.getTime());
		
		long took = (System.currentTimeMillis() - start);
		
		logger.info("Database backup finished, took " + took / 1000 + "sec. filesize is: " + (backupFile.length() / 1024 / 1024) + "mb");
		
		
		Log log = new Log();
		log.setTypeId(LogType.SYSTEM);
		log.setTime(new Date());
		log.setOperation("BACKUP: DATABASE ");
		log.setText("Database backup finished, took " + took / 1000 + "sec. filesize is: " + (backupFile.length() / 1024 / 1024) + "mb");
		log.setHost("SERVER");
		log.setDuration(took);
		logManager.saveLog(log);
		
//		return backupFile;
	}
	
	
	/* (non-Javadoc)
	 * @see lu.tudor.santec.gecamed.backup.ejb.session.interfaces.BackupManagerInterface#getBackupFile(java.lang.String)
	 */
	@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
	public byte[] getBackupFile(String fileName) throws Exception
	{
		File f = new File(BACKUP_FOLDER, fileName);
		return readFile(f);
	}
	
	
	/* (non-Javadoc)
	 * @see lu.tudor.santec.gecamed.backup.ejb.session.interfaces.BackupManagerInterface#listBackups()
	 */
	@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
	public Collection<FileInfo> listBackups() throws Exception
	{
		List<FileInfo> files = new ArrayList<FileInfo>();
		File[] fileArr = new File(BACKUP_FOLDER).listFiles();
		if (fileArr == null)
			return null;
		
		for (int i = 0; i < fileArr.length; i++)
		{
			// not list hidden files
			if (fileArr[i].isHidden())
				continue;
			
			files.add(new FileInfo(fileArr[i]));
		}
		
		Collections.sort(files);
		
		//		for (final Enumeration enumeration = System.getProperties()
		//                .propertyNames(); enumeration.hasMoreElements();) {
		//			String key = (String) enumeration.nextElement();
		//			System.out.println(key+ "=" + System.getProperty(key));
		//		}
		
		return files;
	}
	
	
	/**
	 * helper class that reads the output of the system command
	 * @param p
	 */
	private void readProcessOutput(final Process p)
	{
		error = false;
		output = new StringBuffer();
		
		//		 read system.out
		new Thread()
		{
			public void run()
			{
				try
				{
					String text;
					BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream()));
					while ((text = in.readLine()) != null)
					{
						output.append(text);
						output.append(System.getProperty("line.separator"));
					}
				}
				catch (Exception e)
				{
				}
			}
		}.start();
		
		//	read system.err
		new Thread()
		{
			public void run()
			{
				try
				{
					BufferedReader errin = new BufferedReader(new InputStreamReader(p.getErrorStream()));
					String text;
					while ((text = errin.readLine()) != null)
					{
						output.append(text);
						if (text.indexOf("FATAL:") > 0)
						{
							error = true;
						}
					}
				}
				catch (Exception e)
				{
				}
			}
		}.start();
	}
	
	
	/**
	 * reads the provided file into a byte[]
	* @param file
	* @return
	* @throws IOException
	*/
	private static byte[] readFile(File file) throws IOException
	{
		InputStream is = new FileInputStream(file);
		long length = file.length();
		byte[] bytes = new byte[(int) length];
		int offset = 0;
		int numRead = 0;
		
		
		while (offset < bytes.length && (numRead = is.read(bytes, offset, bytes.length - offset)) >= 0)
		{
			offset += numRead;
		}
		
		try
		{
			if (offset < bytes.length)
				throw new IOException("Could not completely read file " + file.getName());
		}
		finally
		{
			is.close();
		}
		
		return bytes;
	}
	
	
	/* (non-Javadoc)
	 * @see lu.tudor.santec.gecamed.backup.ejb.session.interfaces.BackupManagerInterface#deleteFile(java.lang.String)
	 */
	@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
	public void deleteFile(String fileName)
	{
		long start = System.currentTimeMillis();
		
		File f = new File(BACKUP_FOLDER, fileName);
		f.delete();
		
		if (f.exists()) {
			Log log = new Log();
			log.setTypeId(LogType.SYSTEM);
			log.setTime(new Date());
			log.setOperation("BACKUP: ERROR DELETING OLD FILE");
			log.setText("Unable to delete old backup file" + f.getName() + " (Size:" +(f.length()/1024/1024) + "MB, Writeable:" + f.canWrite() + ")");
			log.setHost("SERVER");
			log.setDuration(System.currentTimeMillis() - start);
			logManager.saveLog(log);	
		} else {
			Log log = new Log();
			log.setTypeId(LogType.SYSTEM);
			log.setTime(new Date());
			log.setOperation("BACKUP: FILE DELETED");
			log.setText("Deleted old backup file" + f.getName());
			log.setHost("SERVER");
			log.setDuration(System.currentTimeMillis() - start);
			logManager.saveLog(log);			
		}
	}
	
	
	/**
	 * @param clearText
	 * @return
	 * @throws NoSuchAlgorithmException
	 * @throws IOException
	 */
	private String getEncodedHash(String clearText) throws NoSuchAlgorithmException, IOException
	{
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		MessageDigest md = MessageDigest.getInstance("MD5");
		if (clearText == null)
		{
			clearText = "";
		}
		byte[] in = clearText.getBytes();
		byte[] digested = md.digest(in);
		baos.write(digested);
		baos.close();
		
		byte[] bArr = Base64.encodeBase64(baos.toByteArray());
		
		return new String(bArr, "ISO-8859-1");
	}
	
	
	/*
	     * (non-Javadoc)
	     * 
	     * @see lu.tudor.santec.gecamed.backup.ejb.session.interfaces.BackupManagerInterface#doFileBackup()
	     */
	@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
	public void doFileBackup(long timestamp) throws Exception
	{
		
		long start = System.currentTimeMillis();
		logger.info("Starting file backup");
		Date backupDate = new Date(timestamp);
		
		//	retrieve Backup File
		File backupFile = new File(BACKUP_FOLDER + File.separator + dbName + "_" + fileDateFormat.format(backupDate) + "." + BACKUP_FILE_EXTENSION);
		backupFile.getParentFile().mkdirs();
		
		
		File dir = new File(FILE_DIR);
		if (!dir.exists())
		{
			logger.info("no files to backup..");
			return;
		}
		
		ArrayList<File> files = FileUtils.getFilesRecursive(dir);
		if (files == null || files.size() == 0)
		{
			logger.info("no files to backup..");
			return;
		}
		
		ZipOutputStream out = new ZipOutputStream(new FileOutputStream(backupFile));
		for (Iterator<File> iter = files.iterator(); iter.hasNext();)
		{
			File f = (File) iter.next();
			
			// not backup the backup folder ;-)
			if (f.getPath().indexOf(BACKUP_FOLDER) >= 0)
				continue;
			
			// not backup the DICOM folder ;-)
			if (f.getPath().indexOf(DICOM_DIR) >= 0)
				continue;
			
			out.putNextEntry(new ZipEntry(f.getPath().replaceAll(FILE_DIR, dir.getName())));
			out.write(FileUtils.readFile(f));
			out.closeEntry();
		}
		out.close();
		
		long took = (System.currentTimeMillis() - start);
		
		logger.info("File backup finished, took " + took / 1000 + "sec. filesize is: " + (backupFile.length() / 1024 / 1024) + "mb");
		output.append("\n").append("File backup finished, took " + took / 1000 + "sec. filesize is: " + (backupFile.length() / 1024 / 1024) + "mb\n");
		
		Log log = new Log();
		log.setTypeId(LogType.SYSTEM);
		log.setTime(new Date());
		log.setOperation("BACKUP: FILE ");
		log.setText("File backup finished, took " + took / 1000 + "sec. filesize is: " + (backupFile.length() / 1024 / 1024) + "mb");
		log.setHost("SERVER");
		log.setDuration(took);
		logManager.saveLog(log);
		
		backupFile.setLastModified(backupDate.getTime());
		
	}
	
	
	/*
     * (non-Javadoc)
     * 
     * @see lu.tudor.santec.gecamed.backup.ejb.session.interfaces.BackupManagerInterface#doFileBackup()
     */
	@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
	public void doDicomBackup(long timestamp) throws Exception
	{
		
		long start = System.currentTimeMillis();
		logger.info("Starting Dicom backup");
		Date backupDate = new Date(timestamp);
		
		//	retrieve Backup File
		File backupFile = new File(BACKUP_FOLDER + File.separator + dbName + "_" + fileDateFormat.format(backupDate) + "." + BACKUP_DICOM_EXTENSION);
		backupFile.getParentFile().mkdirs();
		
		
		File dir = new File(DICOM_DIR);
		if (!dir.exists())
		{
			logger.info("no files to backup..");
			return;
		}
		
		ArrayList<File> files = FileUtils.getFilesRecursive(dir);
		if (files == null || files.size() == 0)
		{
			logger.info("no files to backup..");
			return;
		}
		
		ZipOutputStream out = new ZipOutputStream(new FileOutputStream(backupFile));
		for (Iterator<File> iter = files.iterator(); iter.hasNext();)
		{
			File f = (File) iter.next();
			
			// not backup the backup folder ;-)
			if (f.getPath().indexOf(BACKUP_FOLDER) >= 0)
				continue;
			out.putNextEntry(new ZipEntry(f.getPath().replaceAll(DICOM_DIR, dir.getName())));
			out.write(FileUtils.readFile(f));
			out.closeEntry();
		}
		out.close();
		
		long took = (System.currentTimeMillis() - start);
		
		logger.info("Dicom backup finished, took " + took / 1000 + "sec. filesize is: " + (backupFile.length() / 1024 / 1024) + "mb");
		output.append("\n").append("Dicom backup finished, took " + took / 1000 + "sec. filesize is: " + (backupFile.length() / 1024 / 1024) + "mb\n");
		
		Log log = new Log();
		log.setTypeId(LogType.SYSTEM);
		log.setTime(new Date());
		log.setOperation("BACKUP: DICOM ");
		log.setText("Dicom backup finished, took " + took / 1000 + "sec. filesize is: " + (backupFile.length() / 1024 / 1024) + "mb");
		log.setHost("SERVER");
		log.setDuration(took);
		logManager.saveLog(log);
		
		backupFile.setLastModified(backupDate.getTime());
		
	}
	
	
	/*
	     * (non-Javadoc)
	     * 
	     * @see lu.tudor.santec.gecamed.backup.ejb.session.interfaces.BackupManagerInterface#doFileBackup()
	     */
	@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
	public void doLetterTemplateBackup(long timestamp) throws Exception
	{
		
		long start = System.currentTimeMillis();
		logger.info("Starting Letter Template backup");
		Date backupDate = new Date(timestamp);
		
		//	retrieve Backup File
		File backupFile = new File(BACKUP_FOLDER + File.separator + dbName + "_" + fileDateFormat.format(backupDate) + "." + BACKUP_LETTERTEMPLATE_EXTENSION);
		backupFile.getParentFile().mkdirs();
		
		
		File dir = new File(TEMPLATE_DIR);
		if (!dir.exists())
		{
			logger.info("no files to backup..");
			return;
		}
		
		ArrayList<File> files = FileUtils.getFilesRecursive(dir);
		if (files == null || files.size() == 0)
		{
			logger.info("no files to backup..");
			return;
		}
		
		ZipOutputStream out = new ZipOutputStream(new FileOutputStream(backupFile));
		for (Iterator<File> iter = files.iterator(); iter.hasNext();)
		{
			File f = (File) iter.next();
			
			// not backup the backup folder ;-)
			if (f.getPath().indexOf(BACKUP_FOLDER) >= 0)
				continue;
			out.putNextEntry(new ZipEntry(f.getPath().replaceAll(TEMPLATE_DIR, dir.getName())));
			out.write(FileUtils.readFile(f));
			out.closeEntry();
		}
		out.close();
		
		long took = (System.currentTimeMillis() - start);
		
		logger.info("Template backup finished, took " + took / 1000 + "sec. filesize is: " + (backupFile.length() / 1024 / 1024) + "mb");
		output.append("\n").append("Template backup finished, took " + took / 1000 + "sec. filesize is: " + (backupFile.length() / 1024 / 1024) + "mb\n");
		
		Log log = new Log();
		log.setTypeId(LogType.SYSTEM);
		log.setTime(new Date());
		log.setOperation("BACKUP: LETTER_TEMPLATE ");
		log.setText("Letter Template backup finished, took " + took / 1000 + "sec. filesize is: " + (backupFile.length() / 1024 / 1024) + "mb");
		log.setHost("SERVER");
		log.setDuration(took);
		logManager.saveLog(log);
		
		backupFile.setLastModified(backupDate.getTime());

	}
	
	
	private static Thread autoBackup;
	private static Runnable autoBackupRunnable;
	public void startAutoBackup (final boolean doDbBackup, final boolean doFileBackup, final boolean doDicomBackup, 
			final boolean doTemplateBackup, final int keepBackups, final String usersToNotify)
	{
		if (autoBackupRunnable == null)
		{
			autoBackupRunnable = new Runnable()
			{
				public void run() 
				{
					doBackups(doDbBackup, doFileBackup, doDicomBackup, doTemplateBackup, keepBackups, usersToNotify);
				}
			};
		}
		
		if (autoBackup == null || !autoBackup.isAlive())
		{
			autoBackup = new Thread(autoBackupRunnable);
			autoBackup.start();
		}
	}
	
	
	private void doBackups (final boolean doDbBackup, final boolean doFileBackup, final boolean doDicomBackup, 
			final boolean doTemplateBackup, final int keepBackups, final String usersToNotify)
	{
			BackupSchedulerBean.notifyBackupStarted();
			// start the backup.....
			logger.info("STARTING BACKUP!!!!!!!");
			long timestamp = System.currentTimeMillis();
		
			try
			{
				if (doDbBackup)
				{
					try
					{
						doBackup(timestamp);
						deleteOldFiles(keepBackups, BackupManagerInterface.BACKUP_DB_EXTENSION);
					}
					catch (Throwable e)
					{
						logger.log(Level.ERROR, "Error during database backup!", e);
						notifyUsers(new Date(timestamp), usersToNotify);
						
						Log log = new Log();
						log.setTypeId(LogType.ERROR);
						log.setTime(new Date());
						log.setOperation("BACKUP: DATABASE");
						log.setText("Error while creating the database backup: " + e.getMessage());
						log.setHost("SERVER");
						logManager.saveLog(log);
					}
				}
				
				if (doFileBackup)
				{
					try
					{
						doFileBackup(timestamp);
						deleteOldFiles(keepBackups, BackupManagerInterface.BACKUP_FILE_EXTENSION);
					}
					catch (Throwable e)
					{
						logger.log(Level.ERROR, "Error during file backup!", e);
						notifyUsers(new Date(timestamp), usersToNotify);
						
						Log log = new Log();
						log.setTypeId(LogType.ERROR);
						log.setTime(new Date());
						log.setOperation("BACKUP: FILE");
						log.setText("Error while creating the file backup: " + e.getMessage());
						log.setHost("SERVER");
						logManager.saveLog(log);
					}
				}
				
				if (doDicomBackup)
				{
					try
					{
						doDicomBackup(timestamp);
						deleteOldFiles(keepBackups, BackupManagerInterface.BACKUP_DICOM_EXTENSION);
					}
					catch (Throwable e)
					{
						logger.log(Level.ERROR, "Error during dicom backup!", e);
						notifyUsers(new Date(timestamp), usersToNotify);
						
						Log log = new Log();
						log.setTypeId(LogType.ERROR);
						log.setTime(new Date());
						log.setOperation("BACKUP: DICOM");
						log.setText("Error while creating the dicom backup: " + e.getMessage());
						log.setHost("SERVER");
						logManager.saveLog(log);
					}
				}
				
				if (doTemplateBackup)
				{
					try
					{
						doLetterTemplateBackup(timestamp);
						deleteOldFiles(keepBackups, BackupManagerInterface.BACKUP_LETTERTEMPLATE_EXTENSION);
					}
					catch (Throwable e)
					{
						logger.log(Level.ERROR, "Error during letter template backup!", e);
						notifyUsers(new Date(timestamp), usersToNotify);
						
						Log log = new Log();
						log.setTypeId(LogType.ERROR);
						log.setTime(new Date());
						log.setOperation("BACKUP: LETTER_TEMPLATE");
						log.setText("Error while creating the letter template backup: " + e.getMessage());
						log.setHost("SERVER");
						logManager.saveLog(log);
					}
				}
			} catch (Throwable e)	{
				// log the error
				logger.error("GECAMed Backup FAILED!!!", e);
				notifyUsers(new Date(timestamp), usersToNotify);
			} finally {
				BackupSchedulerBean.notifyBackupFinished();
			}
	}
	
	
	private void notifyUsers(Date backupTime, String usersToNotify)
	{
		// send a message to all that are set in the options
		String backupDate = dateFormatter.format(backupTime);
		List<Integer> userIds = getUsersToNotify(usersToNotify);
		String message = "The GECAMed Backup from " + backupDate + " failed!!!\n" + "La sauvegarde de GECAMed " + backupDate + " échoué!!!\n" + "Das GECAMed Backup vom " + backupDate + " ist fehlgeschlagen!!!";
		UserMessageInterface messenger;
		boolean sendToAllUsers;
		UserMessage userMessage;
		
		sendToAllUsers = userIds.contains(-1);
		messenger = (UserMessageInterface) ManagerFactory.getRemote(UserMessageManager.class);
		
		// define the message to send
		userMessage = new UserMessage();
		userMessage.setAllUser(sendToAllUsers);
		userMessage.setDate(backupTime);
		userMessage.setIsImportant(true);
		userMessage.setIsRead(false);
		userMessage.setMessage(message);
		userMessage.setReceiverId(null);
		userMessage.setSenderId(null);
		
		// send the message
		if (sendToAllUsers)
		{
			try
			{
				// send to all users
				messenger.saveUserMessage(userMessage);
				logger.info("\"Backup-Failed\"-message send to all users");
			}
			catch (Exception e)
			{
				logger.error("Failed to send \"Backup-Failed\"-message all users", e);
			}
		}
		else
		{
			// send to all selected users
			for (Integer userId : userIds)
			{
				try
				{
					userMessage.setReceiverId(userId);
					messenger.saveUserMessage(userMessage);
					logger.info("\"Backup-Failed\"-message send to user " + userId);
				}
				catch (Exception e)
				{
					logger.error("Failed to send \"Backup-Failed\"-message to user " + userId, e);
				}
			}
		}
		
		try {
			messageSender.notify(userMessage);
		} catch (Exception e) {
			logger.warn("Error notifying Users");
		}
		
	}
	
	
	private List<Integer> getUsersToNotify(String userIDs)
	{
		List<Integer> usersToNotify = new LinkedList<Integer>();
		Integer idInt;
		
		for (String idString : userIDs.split(","))
		{
			try
			{
				idInt = Integer.parseInt(idString);
				usersToNotify.add(idInt);
			}
			catch (NumberFormatException e)
			{
			}
		}
		
		return usersToNotify;
	}
	
	
	/**
	 * deletes old backupfiles and only keeps the newest "keepBackups" no. of files
	 * @param keepBackups
	 */
	private void deleteOldFiles(Integer keepBackups, String fileExt)
	{
		try
		{
			int i = 1;
			
			for (FileInfo fi : getBackupFiles(fileExt))
			{
				if (i > keepBackups)
				{
					deleteFile(fi.getName());
					logger.info("deleted old backup: " + fi.getName());
				}
				i++;
			}
		}
		catch (Exception e)
		{
			logger.log(Level.WARN, "error running backup: ", e);
			e.printStackTrace();
			
			Log log = new Log();
			log.setTypeId(LogType.ERROR);
			log.setTime(new Date());
			log.setOperation("BACKUP: FILE DELETED");
			log.setText("Error while deleting the old backup files: " + e.getMessage());
			log.setHost("SERVER");
			logManager.saveLog(log);
		}
	}
	
	
	private Collection<FileInfo> getBackupFiles (String fileExtension)
	{
		File[] backupFileArray = new File(BackupManagerBean.BACKUP_FOLDER).listFiles(getFilter(fileExtension));
		ArrayList<FileInfo> backupFiles = new ArrayList<FileInfo>(backupFileArray.length);
		
		
		for (File f : backupFileArray)
			backupFiles.add(new FileInfo(f));
		Collections.sort(backupFiles);
		
		return backupFiles;
	}
	
	
	private static FileFilter getFilter (final String fileExtension)
	{
		FileFilter filter = filters.get(fileExtension);
		
		
		if (filter == null)
		{
			filter = new FileFilter() 
			{
				private String extension = "."+fileExtension;
				
				public boolean accept(File pathname) 
				{
					return pathname.isFile() && pathname.getName().endsWith(extension);
				}
			};
			filters.put(fileExtension, filter);
		}
		
		return filter;
	}

	@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
	public String getOutput() {
		return output.toString();
	}
}
