/** * 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); } } } }