/******************************************************************************* * Imixs Workflow * Copyright (C) 2001, 2011 Imixs Software Solutions GmbH, * http://www.imixs.com * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You can receive a copy of the GNU General Public * License at http://www.gnu.org/licenses/gpl.html * * Project: * http://www.imixs.org * http://java.net/projects/imixs-workflow * * Contributors: * Imixs Software Solutions GmbH - initial API and implementation * Ralph Soika - Software Developer *******************************************************************************/ package org.imixs.marty.dms; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Collection; import java.util.Comparator; import java.util.Date; import java.util.List; import java.util.logging.Logger; import javax.activation.MimetypesFileTypeMap; import javax.annotation.Resource; import javax.annotation.security.DeclareRoles; import javax.annotation.security.RunAs; import javax.ejb.EJB; import javax.ejb.LocalBean; import javax.ejb.SessionContext; import javax.ejb.Stateless; import javax.ejb.Timeout; import javax.ejb.Timer; import org.imixs.workflow.ItemCollection; import org.imixs.workflow.engine.DocumentService; import org.imixs.workflow.xml.XMLItemCollection; import org.imixs.workflow.xml.XMLItemCollectionAdapter; /** * This EJB implements a TimerService which scans a configured file path for new * files to be imported into BlobWorkitems. Each file which is located in one of * the configured file paths will be added into the blobWorkitem . After the * successful import the file will be automatically removed from the file * system. So this ejb implements a kind of bach-import process. * * * The configuration of the timer is stored by this ejb through the method * saveConfiguration(); The configuration is stored as an entity from the type = * 'configuration' and the txtName = '"org.imixs.marty.dms.scheduler'. * * * * * @author rsoika * */ @Stateless @LocalBean @DeclareRoles({ "org.imixs.ACCESSLEVEL.MANAGERACCESS" }) @RunAs("org.imixs.ACCESSLEVEL.MANAGERACCESS") public class DmsSchedulerService { final static public String TYPE = "configuration"; final static public String DMS_TYPE = "workitemlob"; final static public String NAME = "org.imixs.marty.dms.scheduler"; final static public String DROP_FOLDER = "imixs_dms"; final static public long MAX_FILE_SIZE = 10485760; // 10 MB private static Logger logger = Logger.getLogger(DmsSchedulerService.class .getName()); @EJB private DocumentService documentService; @Resource javax.ejb.TimerService timerService; @Resource SessionContext ctx; /** * This method loads the current scheduler configuration. If still no * configuration entity exists the method returns an empty ItemCollection * * @return */ public ItemCollection findConfiguration() { ItemCollection configItemCollection = null; // String sQuery = "SELECT config FROM Entity AS config " // + " JOIN config.textItems AS t2" + " WHERE config.type = '" // + TYPE + "'" + " AND t2.itemName = 'txtname'" // + " AND t2.itemValue = '" + NAME + "'" // + " ORDER BY t2.itemValue asc"; // Collection<ItemCollection> col = documentService.getDocumentsByType(TYPE); if (col.size() > 0) { configItemCollection = col.iterator().next(); } else { // create default values configItemCollection = new ItemCollection(); try { configItemCollection.replaceItemValue("type", TYPE); configItemCollection.replaceItemValue("txtname", NAME); } catch (Exception e) { e.printStackTrace(); } } updateTimerDetails(configItemCollection); return configItemCollection; } /** * This method saves the timer configuration. The method ensures that the * following properties are set to default. * <ul> * <li>type</li> * <li>txtName</li> * <li>$writeAccess</li> * <li>$readAccess</li> * </ul> * * @return * @throws Exception */ public ItemCollection saveConfiguration(ItemCollection configItemCollection) throws Exception { // update write and read access configItemCollection.replaceItemValue("type", TYPE); configItemCollection.replaceItemValue("txtName", NAME); configItemCollection.replaceItemValue("$writeAccess", "org.imixs.ACCESSLEVEL.MANAGERACCESS"); configItemCollection.replaceItemValue("$readAccess", "org.imixs.ACCESSLEVEL.MANAGERACCESS"); // save entity configItemCollection = documentService.save(configItemCollection); updateTimerDetails(configItemCollection); return configItemCollection; } /** * This Method starts the TimerService. * * The method loads the configuration and evaluates the the following * informations: * * datstart - Date Object * * datstop - Date Object * * numInterval - Integer Object (interval in seconds) * * id - String - unique identifier for the Timer Service. * * The method throws an exception if the configuration entity contains * invalid attributes or values. * * After the timer was started the configuration is updated with the latest * statusmessage * * The method returns the current configuration */ public ItemCollection start() throws Exception { ItemCollection configItemCollection = findConfiguration(); if (configItemCollection == null) return null; // configuration = loadConfiguration(); String id = configItemCollection.getItemValueString("$uniqueid"); Date startDate = configItemCollection.getItemValueDate("datstart"); Date endDate = configItemCollection.getItemValueDate("datstop"); // compute interval int hours = configItemCollection.getItemValueInteger("hours"); int minutes = configItemCollection.getItemValueInteger("minutes"); long interval = (hours * 60 + minutes) * 60 * 1000; configItemCollection .replaceItemValue("numInterval", new Long(interval)); // try to cancel an existing timer for this workflowinstance Timer timer = this.findTimer(id); if (timer != null) timer.cancel(); // if endDate is in the past we do not start the timer! Calendar calNow = Calendar.getInstance(); Calendar calEnd = Calendar.getInstance(); if (endDate != null) calEnd.setTime(endDate); if (calNow.after(calEnd)) { logger.info("[DmsSchedulerService] " + configItemCollection.getItemValueString("txtName") + " not started because stop-date is in the past"); updateTimerDetails(configItemCollection); return configItemCollection; } // start and set statusmessage SimpleDateFormat dateFormatDE = new SimpleDateFormat( "dd.MM.yy hh:mm:ss"); String msg = "started at " + dateFormatDE.format(calNow.getTime()) + " by " + ctx.getCallerPrincipal().getName(); configItemCollection.replaceItemValue("statusmessage", msg); XMLItemCollection xmlConfigItem = null; try { xmlConfigItem = XMLItemCollectionAdapter .putItemCollection(configItemCollection); } catch (Exception e) { logger.severe("Unable to serialize confitItemCollection into a XML object"); e.printStackTrace(); return null; } timer = timerService.createTimer(startDate, interval, xmlConfigItem); logger.info("[DmsSchedulerService] " + configItemCollection.getItemValueString("txtName") + " started: " + id); saveConfiguration(configItemCollection); return configItemCollection; } /** * Cancels a running timer instance. After cancel a timer the configuration * will be updated. * */ public ItemCollection stop() throws Exception { ItemCollection configItemCollection = findConfiguration(); String id = configItemCollection.getItemValueString("$uniqueid"); Timer timer = this.findTimer(id); if (timer != null) { timer.cancel(); Calendar calNow = Calendar.getInstance(); SimpleDateFormat dateFormatDE = new SimpleDateFormat( "dd.MM.yy hh:mm:ss"); String msg = "stopped at " + dateFormatDE.format(calNow.getTime()) + " by " + ctx.getCallerPrincipal().getName(); configItemCollection.replaceItemValue("statusmessage", msg); logger.info("[DmsSchedulerService] " + configItemCollection.getItemValueString("txtName") + " stopped: " + id); } else { configItemCollection.replaceItemValue("statusmessage", ""); } configItemCollection = saveConfiguration(configItemCollection); return configItemCollection; } public boolean isRunning() { try { ItemCollection configItemCollection = findConfiguration(); if (configItemCollection == null) return false; return (findTimer(configItemCollection .getItemValueString("$uniqueid")) != null); } catch (Exception e) { e.printStackTrace(); return false; } } /** * Diese Methode liest Datenaustausch-Daten ein, vearbeitet sie und * verschiebt diese anschließend. * * @param timer */ @Timeout public void runTimer(javax.ejb.Timer timer) { scan(); ItemCollection configItemCollection = findConfiguration(); Date endDate = configItemCollection.getItemValueDate("datstop"); String sTimerID = configItemCollection.getItemValueString("$uniqueid"); /* * Check if Timer should be canceld now? */ Calendar calNow = Calendar.getInstance(); if (endDate != null && calNow.getTime().after(endDate)) { timer.cancel(); System.out.println("[DmsSchedulerService] Timeout sevice stopped: " + sTimerID); } } /** * This method scans a file path and imports the files into blob workitems * After a file was imported it will be removed from the directory. * * The method did only scan a directory containing the folder name * 'imixs_dms'. This is for security reason to avoid the uncontrolled * parsing of os root folders. * * * @param aLog * @throws Exception */ public void scan() { logger.info("[DmsSchedulerService] scanning directories..."); ItemCollection configItemCollection = findConfiguration(); // get all import paths.... List<?> vList = configItemCollection.getItemValue("_filepath"); for (Object aPath : vList) { // test if if (!aPath.toString().contains(DROP_FOLDER)) { logger.warning("[DmsSchedulerService] invalid file path : " + aPath.toString()); logger.warning("[DmsSchedulerService] make sure path contains " + DROP_FOLDER); } else scanPath(aPath.toString()); } } /** * This method returns a timer for a corresponding id if such a timer object * exists. * * @param id * @return Timer * @throws Exception */ private Timer findTimer(String id) throws Exception { for (Object obj : timerService.getTimers()) { Timer timer = (javax.ejb.Timer) obj; if (timer.getInfo() instanceof XMLItemCollection) { XMLItemCollection xmlItemCollection = (XMLItemCollection) timer .getInfo(); ItemCollection adescription = XMLItemCollectionAdapter .getItemCollection(xmlItemCollection); if (id.equals(adescription.getItemValueString("$uniqueid"))) { return timer; } } } return null; } private void updateTimerDetails(ItemCollection configuration) { if (configuration == null) return; String id = configuration.getItemValueString("$uniqueid"); Timer timer; try { timer = this.findTimer(id); if (timer != null) { // load current timer details configuration.replaceItemValue("nextTimeout", timer.getNextTimeout()); configuration.replaceItemValue("timeRemaining", timer.getTimeRemaining()); } else { configuration.removeItem("nextTimeout"); configuration.removeItem("timeRemaining"); } } catch (Exception e) { logger.warning("[DmsSchedulerService] unable to updateTimerDetails: " + e.getMessage()); configuration.removeItem("nextTimeout"); configuration.removeItem("timeRemaining"); } } /** * The method did recursively scan a given file path all files found will be * imported into a blob workitem and after that the file will be removed. * * @param path * - the file path */ private void scanPath(String path) { logger.info("[DmsSchedulerService] scanPath: " + path); File root = new File(path); File[] list = root.listFiles(); for (File f : list) { if (f.isDirectory()) { // Recursive method call scanPath(f.getAbsolutePath()); } else { // start import.... importFile(f); } } } /** * This method imports a file into a blobWorkitem. After a successful import * the fill will be removed. * * * @throws IOException */ private void importFile(File importFile) { logger.info("[DmsSchedulerService] importing file:" + importFile.getAbsoluteFile()); try { InputStream is = new FileInputStream(importFile); // Get the size of the file long length = importFile.length(); if (length > MAX_FILE_SIZE) { logger.warning("[DmsSchedulerService] file is to large - maximum size alowed = " + MAX_FILE_SIZE); is.close(); return; } // Create the byte array to hold the data byte[] bytes = new byte[(int) length]; // Read in the bytes int offset = 0; int numRead = 0; while (offset < bytes.length && (numRead = is.read(bytes, offset, bytes.length - offset)) >= 0) { offset += numRead; } // Ensure all the bytes have been read in if (offset < bytes.length) { is.close(); throw new IOException("Could not completely read file " + importFile.getName()); } // Close the input stream and return bytes is.close(); ItemCollection dmsItemCollection = new ItemCollection(); dmsItemCollection.replaceItemValue("type", DMS_TYPE); dmsItemCollection.replaceItemValue("txtname", importFile.getName()); dmsItemCollection.addFile(bytes, importFile.getName(), new MimetypesFileTypeMap().getContentType(importFile)); dmsItemCollection.replaceItemValue("$writeAccess", "org.imixs.ACCESSLEVEL.MANAGERACCESS"); dmsItemCollection.replaceItemValue("$readAccess", "org.imixs.ACCESSLEVEL.MANAGERACCESS"); // add an empty reference dmsItemCollection.replaceItemValue("$uniqueidRef", ""); // save item collection documentService.save(dmsItemCollection); // delete the file importFile.delete(); } catch (Exception e) { logger.warning("[DmsSchedulerService] error importing file " + importFile.getAbsoluteFile()); logger.warning("[DmsSchedulerService] " + e.getMessage()); } } /** * compares file names * * @author rsoika * */ class FileComparator implements Comparator<File> { public int compare(File a, File b) { long lFileA = a.lastModified(); long lFileB = b.lastModified(); if (lFileA == lFileB) return 0; if (lFileA > lFileB) return 1; else return -1; } } }