package org.imixs.marty.plugins;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Logger;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import org.imixs.workflow.ItemCollection;
import org.imixs.workflow.WorkflowKernel;
import org.imixs.workflow.engine.plugins.AbstractPlugin;
import org.imixs.workflow.exceptions.ModelException;
import org.imixs.workflow.exceptions.PluginException;
import org.imixs.workflow.xml.XMLItemCollection;
import org.imixs.workflow.xml.XMLItemCollectionAdapter;
/**
* The Archive Plug-in stores workitems to disk when reached the type
* 'workitemarchive'. The plug-in reads properties to define the target
* filesystem.
*
* archive.path = output directory
*
*
* @author rsoika
*
*/
public class ArchivePlugin extends AbstractPlugin {
public final static String IO_ERROR = "IO_ERROR";
public final static String DEFAULT_PROTOCOLL = "file://";
public final static String BLOBWORKITEMID = "$BlobWorkitem";
private static Logger logger = Logger.getLogger(ArchivePlugin.class.getName());
/**
* Archive if workitem type changed to 'workitemarchive'
*
* Therefore we compare the current task with the next task object.
*
* @throws PluginException
*
**/
@Override
public ItemCollection run(ItemCollection documentContext, ItemCollection event) throws PluginException {
Map<String, List<Object>> files = null;
// get numNextProcessID and modelVersion
int iNextProcessID = event.getItemValueInteger("numNextProcessID");
int iProcessID = documentContext.getItemValueInteger("$processid");
ItemCollection currentTask = null;
ItemCollection nextTask = null;
// get task objects from model version
String modelVersion = event.getItemValueString("$modelVersion");
try {
currentTask = getCtx().getModelManager().getModel(modelVersion).getTask(iProcessID);
nextTask = getCtx().getModelManager().getModel(modelVersion).getTask(iNextProcessID);
} catch (ModelException e) {
logger.warning("Warning - Task '" + iNextProcessID + "' is not defined by model version '" + modelVersion
+ "' : " + e.getMessage());
return documentContext;
}
// check if target task type is "workitemarchive" or target type isEmpty
// and current type is "workitemarchive".
// note: we need to compare the task objects, because the
// documentContext can already be updated by the ApplicationPlugin!
String targetTaskType = nextTask.getItemValueString("txttype");
if ("workitemarchive".equals(targetTaskType)
|| (targetTaskType.isEmpty() && "workitemarchive".equals(currentTask.getItemValueString("txttype")))) {
// run archive mode!
String archivePath = computeArchivePath(documentContext);
// load the blobWorkitem to get the filelist
ItemCollection blobWorkitem = BlobWorkitemHandler.load(this.getWorkflowService().getDocumentService(),
documentContext);
if (blobWorkitem != null) {
files = blobWorkitem.getFiles();
} else {
// no blobworkitem - so we got the files form the current
// documentContext
files = documentContext.getFiles();
}
writeFiles(archivePath, files);
// clone workitem and remove file content if available...
ItemCollection clone = (ItemCollection) documentContext.clone();
clone.replaceItemValue("$file", getCleanFileContent(files));
// convert the ItemCollection into a XMLItemcollection...
XMLItemCollection xmlItemCollection;
try {
xmlItemCollection = XMLItemCollectionAdapter.putItemCollection(clone);
// marshal the Object into an XML Stream....
JAXBContext context = JAXBContext.newInstance(XMLItemCollection.class);
Marshaller m = context.createMarshaller();
Path file = Paths.get(archivePath + "document.xml");
m.marshal(xmlItemCollection, file.toFile());
} catch (Exception e) {
logger.severe("failed to archive document " + documentContext.getUniqueID() + " - " + e.getMessage());
throw new PluginException(ArchivePlugin.class.getSimpleName(), IO_ERROR,
"failed to archive document " + documentContext.getUniqueID() + "(" + e.getMessage() + ")", e);
}
logger.fine("Document '" + documentContext.getItemValueString(WorkflowKernel.UNIQUEID)
+ "' sucessfull archived");
}
return documentContext;
}
/**
* This method writes the files assigned to the current workitem to disk
*
* @throws PluginException
*
*/
@SuppressWarnings("unused")
private void writeFiles(String archivePath, Map<String, List<Object>> files) throws PluginException {
if (files == null) {
return;
}
// iterate over files..
for (Entry<String, List<Object>> entry : files.entrySet()) {
String sFileName = archivePath + entry.getKey();
List<?> file = entry.getValue();
// if data size >0 transfer file to filesystem
if (file.size() >= 2) {
String contentType = (String) file.get(0);
byte[] data = (byte[]) file.get(1);
if (data != null && data.length > 1) {
// write file...
logger.fine("archive file " + sFileName);
Path newfile = Paths.get(sFileName);
try {
Files.write(newfile, data);
} catch (IOException e) {
logger.severe("Unable to archive file " + sFileName + " - " + e.getMessage());
throw new PluginException(ArchivePlugin.class.getSimpleName(), IO_ERROR,
"failed to write file " + sFileName + " to disk (" + e.getMessage() + ")", e);
}
}
}
}
}
/**
* This method constructs the archive path form the imixs.properties
* archive.path and the workitem properties $modified, $uniqueid and
* txtWorkflowGroup
*
* /ARCHIVE_PATH/YYYY/WORKFLOWGROUP/UNIQUEID/
*
* @return
* @throws PluginException
*/
private String computeArchivePath(ItemCollection documentContext) throws PluginException {
String archivePath = this.getWorkflowService().getPropertyService().getProperties().getProperty("archive.path",
"archive");
if (archivePath.endsWith(FileSystems.getDefault().getSeparator())) {
archivePath = archivePath + FileSystems.getDefault().getSeparator();
}
// build path YEAR/WORKFLOWGRUP/UNIQUEID
SimpleDateFormat formatter = new SimpleDateFormat("yyyy");
Date modified = documentContext.getItemValueDate("$modified");
if (modified == null) {
logger.warning("Document " + documentContext.getUniqueID() + " does not provide $modified!");
modified = new Date();
}
String group = documentContext.getItemValueString("txtworkflowgroup");
if (group.isEmpty()) {
logger.warning("Document " + documentContext.getUniqueID() + " does not provide txtworkflowgroup!");
group = "default";
}
archivePath = archivePath + FileSystems.getDefault().getSeparator() + formatter.format(modified)
+ FileSystems.getDefault().getSeparator() + group + FileSystems.getDefault().getSeparator()
+ documentContext.getUniqueID() + FileSystems.getDefault().getSeparator();
logger.finest("archive path = " + archivePath);
// now create dirs...
try {
Files.createDirectories(Paths.get(archivePath));
} catch (IOException e) {
logger.severe("failed to create archive directory '" + archivePath + "' - " + e.getMessage());
throw new PluginException(ArchivePlugin.class.getSimpleName(), IO_ERROR,
"failed to create archive directory '" + archivePath + "' (" + e.getMessage() + ")", e);
}
return archivePath;
}
/**
* This method clears the File content of the workitem if available. We
* don't want to write the files into the xml stream.
*
* @param aWorkitem
* @param aBlobWorkitem
*/
private Map<String, List<Object>> getCleanFileContent(Map<String, List<Object>> files) {
Map<String, List<Object>> cleanFileList = new HashMap<String, List<Object>>();
if (files == null) {
return cleanFileList;
}
for (Entry<String, List<Object>> entry : files.entrySet()) {
String sFileName = entry.getKey();
List<?> file = entry.getValue();
// if data size >0 transfer file into blob
if (file.size() >= 2) {
String contentType = (String) file.get(0);
byte[] empty = { 0 };
List<Object> list = new ArrayList<Object>();
list.add(contentType);
list.add(empty);
cleanFileList.put(sFileName, list);
}
}
return cleanFileList;
}
}