/**
* MailArchiver is an application that provides services for storing and managing e-mail messages through a Web Services SOAP interface.
* Copyright (C) 2012 Marcio Andre Scholl Levien and Fernando Alberto Reuter Wendt and Jose Ronaldo Nogueira Fonseca Junior
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
/******************************************************************************\
*
* This product was developed by
*
* SERVIÇO FEDERAL DE PROCESSAMENTO DE DADOS (SERPRO),
*
* a government company established under Brazilian law (5.615/70),
* at Department of Development of Porto Alegre.
*
\******************************************************************************/
package serpro.mailarchiver.service.web;
import java.io.IOException;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.nio.file.Files;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import javax.jdo.annotations.PersistenceAware;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import org.codehaus.jettison.mapped.Configuration;
import org.codehaus.jettison.mapped.MappedXMLInputFactory;
import org.codehaus.staxmate.SMInputFactory;
import org.codehaus.staxmate.in.SMHierarchicCursor;
import org.codehaus.staxmate.in.SMInputCursor;
import org.springframework.beans.factory.annotation.Autowired;
import com.ctc.wstx.stax.WstxInputFactory;
import de.schlichtherle.truezip.file.TFile;
import de.schlichtherle.truezip.nio.file.TPath;
import de.schlichtherle.truezip.fs.FsSyncException;
import serpro.mailarchiver.domain.metaarchive.Folder;
import serpro.mailarchiver.domain.metaarchive.Message;
import serpro.mailarchiver.service.BaseService;
import serpro.mailarchiver.service.find.FFolder;
import serpro.mailarchiver.service.find.FMessage;
import serpro.mailarchiver.util.Logger;
import serpro.mailarchiver.util.UserAppConfig;
import serpro.mailarchiver.util.transaction.WithReadOnlyTx;
@PersistenceAware
public class DefaultZipMessagesOperation
extends BaseService
implements ZipMessagesOperation
{
private static final Logger log = Logger.getLocalLogger();
@Autowired
private FMessage findMessage;
@Autowired
private FFolder findFolder;
@Autowired
private UserAppConfig userAppConfig;
@WithReadOnlyTx
@Override
public String apply(String zipConfig) throws ServiceFault {
/*
* zipConfig:
*
*
*
*
*
*
*
*
*
*/
if(zipConfig.isEmpty()) {
ServiceFault.invalidZipConfig()
.setActor("zipMessages")
.setMessage("Zip config is null or empty.")
.raise();
}
XMLInputFactory inf = null;
switch(zipConfig.charAt(0)) {
case '<':
inf = new WstxInputFactory();
break;
case '{':
Configuration config = new Configuration();
config.setIgnoreNamespaces(true);
inf = new MappedXMLInputFactory(config);
break;
default:
ServiceFault.invalidZipConfig()
.setActor("zipMessages")
.setMessage("Invalid zip config.")
.raise();
}
String guid = null;
SMInputFactory sminf = new SMInputFactory(inf);
StringReader strReader = new StringReader(zipConfig);
try {
SMHierarchicCursor cursor0 = sminf.rootElementCursor(strReader);
cursor0.advance();
String localName0 = cursor0.getLocalName();
if(localName0.equalsIgnoreCase("zip")) {
String format = "zip";
for(int i = 0; i < cursor0.getAttrCount(); i++) {
String attrLocalName0 = cursor0.getAttrLocalName(i);
if(attrLocalName0.equalsIgnoreCase("format")) {
format = cursor0.getAttrValue(i);
}
else {
//ignore ?
}
}
if((!format.equalsIgnoreCase("zip"))
&& (!format.equalsIgnoreCase("jar"))
&& (!format.equalsIgnoreCase("tar"))
&& (!format.equalsIgnoreCase("tar.gz"))
&& (!format.equalsIgnoreCase("tgz"))
&& (!format.equalsIgnoreCase("tar.bz2"))
&& (!format.equalsIgnoreCase("tb2"))
&& (!format.equalsIgnoreCase("tbz"))) {
ServiceFault.unsupportedArchiveFormat()
.setActor("zipMessages")
.setMessage("Unsupported archive format.")
.addValue("format", format)
.raise();
}
List messages = new ArrayList();
SMInputCursor cursor1 = cursor0.childElementCursor();
while(cursor1.getNext() != null) {
String localName1 = cursor1.getLocalName();
if(localName1.equalsIgnoreCase("message")) {
String messageId = "";
for(int i = 0; i < cursor1.getAttrCount(); i++) {
String attrLocalName1 = cursor1.getAttrLocalName(i);
if(attrLocalName1.equalsIgnoreCase("id")) {
messageId = cursor1.getAttrValue(i);
}
else {
//ignore ?
}
}
addMessage(messages, messageId);
}
else if(localName1.equalsIgnoreCase("folder")) {
String folderId = "";
boolean recursive = false;
for(int i = 0; i < cursor1.getAttrCount(); i++) {
String attrLocalName1 = cursor1.getAttrLocalName(i);
if(attrLocalName1.equalsIgnoreCase("id")) {
folderId = cursor1.getAttrValue(i);
}
else if(attrLocalName1.equalsIgnoreCase("recursive")) {
recursive = cursor1.getAttrBooleanValue(i);
}
else {
//ignore ?
}
}
addFolder(messages, folderId, recursive);
}
else {
//ignore ?
}
}
Collections.sort(messages, new Comparator() {
@Override
public int compare(Message m1, Message m2) {
return m1.getRelativePath().compareTo(m2.getRelativePath());
}
});
try {
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(format.getBytes("UTF-8"));
for(Message message : messages) {
md.update(message.getOid().getBytes("UTF-8"));
}
char[] hexDigest = new char[32];
int i = 0;
for(byte b : md.digest()) {
hexDigest[i++] = Character.forDigit((char)((b & 0xf0) >>> 4), 16);
hexDigest[i++] = Character.forDigit((char)(b & 0x0f), 16);
}
String digest = new String(hexDigest);
guid = digest.substring(0, 8) +
"-" + digest.substring(8, 12) +
"-" + digest.substring(12, 16) +
"-" + digest.substring(16, 20) +
"-" + digest.substring(20, 32);
}
catch(NoSuchAlgorithmException ex) {
}
catch(UnsupportedEncodingException ex) {
}
TPath zip = new TPath(userAppConfig.SERVER.getArchiveDir().resolve("temp").resolve("mails_" + guid + "." + format));
if(Files.notExists(zip)) {
try {
for(Message message : messages) {
TFile.cp(message.getAbsolutePath().toFile(), zip.resolve(message.getRelativePath()).toFile());
}
}
catch(IOException ex) {
ServiceFault.fileSystemFailure()
.setActor("zipMessages")
.setMessage("Filesystem archive message failure.")
.setCause(ex)
.raise();
}
finally {
try {
TFile.umount(zip.toFile(), true);
}
catch(FsSyncException ex) {
ServiceFault.fileSystemFailure()
.setActor("zipMessages")
.setMessage("Archive umount failure.")
.setCause(ex)
.raise();
}
}
}
}
else {
//ignore ?
}
}
catch(XMLStreamException ex) {
ServiceFault.runtimeException()
.setActor("zipMessages")
.setMessage("Zip config parse exception.")
.setCause(ex)
.raise();
}
return guid;
}
private void addMessage(List messages, String messageId) throws ServiceFault {
if(messageId.isEmpty()) {
ServiceFault.invalidMessageId()
.setActor("zipMessages")
.setMessage("Message id is empty or null.")
.raise();
}
Message message = findMessage.byId(messageId);
if(message == null) {
ServiceFault.messageNotFound()
.setActor("zipMessages")
.setMessage("Message not found.")
.addValue("messageId", messageId)
.raise();
}
if( ! messages.contains(message)) {
messages.add(message);
}
}
private void addFolder(List messages, String folderId, boolean recursive) throws ServiceFault {
Folder folder = findFolder.byId(folderId);
if(folder == null) {
ServiceFault.folderNotFound()
.setActor("zipMessages")
.setMessage("Folder not found.")
.addValue("folderId", folderId)
.raise();
}
addFolder(messages, folder, recursive);
}
private void addFolder(List messages, Folder folder, boolean recursive) {
for(Message message : folder.getMessages()) {
if( ! messages.contains(message)) {
messages.add(message);
}
}
if(recursive) {
for(Folder child : folder.getChildren()) {
addFolder(messages, child, recursive);
}
}
}
}