/* Index ECM Engine - A system for managing the capture (when created * or received), classification (cataloguing), storage, retrieval, * revision, sharing, reuse and disposition of documents. * * Copyright (C) 2008 Regione Piemonte * Copyright (C) 2008 Provincia di Torino * Copyright (C) 2008 Comune di Torino * * 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, * 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 should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * */ package it.doqui.index.ecmengine.business.personalization.importer; import it.doqui.index.ecmengine.business.job.JobBusinessInterface; import it.doqui.index.ecmengine.business.job.dto.BatchJob; import it.doqui.index.ecmengine.business.job.dto.BatchJobParam; import it.doqui.index.ecmengine.business.job.util.EncryptionHelper; import it.doqui.index.ecmengine.business.job.util.JobStatus; import it.doqui.index.ecmengine.business.personalization.multirepository.RepositoryManager; import it.doqui.index.ecmengine.business.personalization.multirepository.Repository; import it.doqui.index.ecmengine.business.personalization.multirepository.ContentStoreDefinition; import it.doqui.index.ecmengine.dto.backoffice.DataArchive; import it.doqui.index.ecmengine.exception.EcmEngineException; import org.alfresco.service.cmr.security.AuthenticationService; import static it.doqui.index.ecmengine.util.EcmEngineConstants.*; import static it.doqui.index.ecmengine.business.personalization.importer.ArchiveImporterJobConstants.*; import it.doqui.index.ecmengine.exception.EcmEngineFoundationException; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.DuplicateChildNodeNameException; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.transaction.TransactionService; import org.alfresco.model.ContentModel; import org.alfresco.service.namespace.QName; import javax.transaction.RollbackException; import org.alfresco.util.TempFileProvider; import it.doqui.index.ecmengine.dto.OperationContext; import java.util.Map; import java.util.List; import java.io.FileReader; import java.io.Serializable; import java.util.HashMap; import javax.activation.MimetypesFileTypeMap; import javax.transaction.UserTransaction; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileOutputStream; import java.io.FileInputStream; import java.io.InputStream; import java.util.zip.GZIPInputStream; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import org.apache.tools.tar.TarEntry; import org.apache.tools.tar.TarInputStream; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import java.nio.channels.FileChannel; import java.io.IOException; /** * Classe che implementa il job ... * * @author DoQui * */ public class ArchiveImporterJob implements Job { // Logger private static Log logger = LogFactory.getLog(ECMENGINE_ROOT_LOG_CATEGORY); // Variabile privata per verificare se il JOB e' in esecuzione // Occorre infatti l'uso in sigleton di archive importer private static boolean running = false; // Service da utilizzare per l'importazione dei contenuti private TransactionService transactionService; private ContentService contentService; private NodeService nodeService; private NamespaceService namespaceService; private AuthenticationService authenticationService; /** Tipo predefinito per i file. */ public static final String DEFAULT_CONTENT_TYPE = "cm:content"; /** Tipo predefinito per i folder. */ public static final String DEFAULT_CONTAINER_TYPE = "cm:folder"; /** Property contenente il nome dei file. */ public static final String DEFAULT_CONTENT_NAME_PROPERTY = "cm:name"; /** Property contenente il nome dei folder. */ public static final String DEFAULT_CONTAINER_NAME_PROPERTY = "cm:name"; /** Tipo di associazione predefinito per i folder. */ public static final String DEFAULT_CONTAINER_ASSOC_TYPE = "cm:contains"; /** Tipo di associazione predefinito per i file. */ public static final String DEFAULT_PARENT_ASSOC_TYPE = "cm:contains"; /* (non-Javadoc) * @see org.quartz.Job#execute(org.quartz.JobExecutionContext) */ public void execute(JobExecutionContext context) throws JobExecutionException { logger.debug("[ArchiveImporterJob::execute] BEGIN"); if (!running) { synchronized (this) { if (!running) { running = true; } else { logger.debug("[ArchiveImporterJob::execute] job already running 1"); logger.debug("[ArchiveImporterJob::execute] END"); return; } } } else { logger.debug("[ArchiveImporterJob::execute] job already running 2"); logger.debug("[ArchiveImporterJob::execute] END"); return; } logger.debug("[ArchiveImporterJob::execute] START"); JobBusinessInterface jobManager = null; BatchJob batchJob = null; try { // Chiedo un'istanza di JobManager jobManager = (JobBusinessInterface)context.getJobDetail().getJobDataMap().get(ECMENGINE_JOB_MANAGER_BEAN); // In caso di successo if( jobManager!=null ){ List<Repository> repositories = RepositoryManager.getInstance().getRepositories(); for (Repository repository : repositories) { logger.debug("[ArchiveImporterJob::execute] import archive on repository '"+repository.getId()+"'"); RepositoryManager.setCurrentRepository(repository.getId()); // Faccio la lista dei job di tipo ECMENGINE_ARCHIVE_IMPORTER_JOB_REF, attivi // Possono essere // Ready - da processare // Running - in esecuzione : sto importando il file // Finished - ho finito // In questo stato, essendo un job singleton, dovrei avere solo stati ready (nuovi job) o finished (job finiti) // Se icontro un RUNNING, sicuramente si tratta di una condizione di errore, riporto a ready il job, e faccio in // modo che l'algoritmo sottostante continui l'importazione in modo incrementale // Prendo tutti i job di un certo esecutore: in futuro, se la cosa dovesse dare problemi di performance // aggiungere un filtro sullo status RUNNING BatchJob[] bjs = jobManager.getJobsByExecutor( ECMENGINE_ARCHIVE_IMPORTER_JOB_REF ); if( bjs!=null ){ // Se ho dei BatchJob for( BatchJob bj : bjs ){ logger.debug("[ArchiveImporterJob::execute] job status " +bj.getId() +":" +bj.getStatus()); // Se lo stato e' running if( bj.getStatus().equalsIgnoreCase( JobStatus.RUNNING ) ){ logger.debug("[ArchiveImporterJob::execute] update status to ready " +bj.getId() ); // Reimposto lo stato a ready bj.setStatus( JobStatus.READY ); jobManager.updateJob(bj); } } } else { logger.debug("[ArchiveImporterJob::execute] BatchJob NULL per " +ECMENGINE_ARCHIVE_IMPORTER_JOB_REF ); } // A questo punto, sono in una situazione consistente, con dei job ready // I job ready possono essere sia nuovi job, che resume di situazioni precedenti while( (batchJob=jobManager.getNextJob( ECMENGINE_ARCHIVE_IMPORTER_JOB_REF ))!=null ){ // Prendo il prossimo job logger.debug("[ArchiveImporterJob::execute] start batchJob " +batchJob.getId()); if( batchJob!=null ){ try { // Estraggo i parametri di esecuzione BatchJobParam pName = batchJob.getParam(PARAM_NAME ); BatchJobParam pFormat = batchJob.getParam(PARAM_FORMAT ); BatchJobParam pStore = batchJob.getParam(PARAM_CONTENTSTORE_DIR ); String importDirectory = (String)context.getJobDetail().getJobDataMap().get(ECMENGINE_IMPORT_DIRECTORY); // verifico se sono corretti checkParam( batchJob, pName , "Archive name not found" ); checkParam( batchJob, pFormat , "Archive format not found" ); checkParam( batchJob, pStore , "Archive store not found" ); checkParam( batchJob, importDirectory , "importDirectory null" ); // Validati i parametri di estrazione del fie, procedo con lo // spostamento da TEMP alla directory di import, ed esplodo il file // Sposto il file dalla temp alla workdir del progetto String cFrom = pStore.getValue() +File.separator +pName.getValue(); File oFrom = new File(cFrom); logger.debug( "[ArchiveImporterJob::execute] From:" +cFrom ); // Crea la directory di output se non esiste File oDir = new File(importDirectory); if( !oDir.exists() ){ oDir.mkdir(); } // File di importazione String cFile = importDirectory +File.separator +pName.getValue(); File oFile = new File(cFile); logger.debug( "[ArchiveImporterJob::execute] Import:" +cFile ); // Se esiste il file, lo sposto nella dir dove deve essere processato if( oFrom.exists() ){ // Provo lo spostamento, perche' piu' veloce if( !oFrom.renameTo( oFile ) ){ // Se fallisce provo la copia, piu' lenta if( !copyFile(oFrom, oFile) ){ batchJob.setMessage("Unable to copy from (" +cFrom +") to (" +cFile +")"); throw new EcmEngineException("ArchiveImporterJob: " +batchJob.getMessage()); } else { // Se la copia va a buon fine, cancello il file oFrom.delete(); } } } // Prima cosa, provo a caricare in RAM il file indicato. // Attenzione: vista la dimensione a 256 char dei parametri dei job, // ho preferito spezzare path e nome file in due variabili diverse, in modo // da all'ungare il piu' possibile la lunghezza del path utilizzabile // Una volta processato il file, lo rinomino in processed, per poterne tenere traccia // I processed andrebbero alimintati ogni tanto, per ridurne la coda // Alternativamente, si puo' variare il codice per rinominarli al posto di rinominare // Un'altra ragione della rinomina e' data dal fatto che, in caso di zip corrotti, puo' accadere che // il file risulti importato, ma non presente, senza nessun messaggio d'errore String cFileRenamed = cFile +".processed"; File oFileRenamed = new File( cFileRenamed ); // Il temp path lo formo col nome del file compresso +".extract", in modo da avere l'univocita' data gia // dal nome file String tempPath = cFile +".extract"; File tmpWorkDir = new File(tempPath); // Se il file non esiste if( !oFile.exists() ){ logger.debug("[ArchiveImporterJob::execute] File di importazione non presente, controllo directory di esplosione"); // Se ho la dir di estrazione, vuol dire che stavo importando e c'e' stato un errore // Quindi do errore solo se non esiste manco la dir di output if( !tmpWorkDir.exists() ) { batchJob.setMessage("Archive not found"); throw new EcmEngineException("ArchiveImporterJob: " +batchJob.getMessage() +" (" +cFile +")"); } else { logger.debug("[ArchiveImporterJob::execute] Directory di importazione presente, procedo con l'importazione"); } } // Se sono qui, posso avere il file o la dir di esplosione // Se ho il file, prendo il contenuto da disco e lo decomprimo if( oFile.exists() ){ byte[] content = getBinary( cFile ); logger.debug( "[ArchiveImporterJob::execute] Content size: " +content.length ); // La directory la creo solo se non esiste, devo infatti gestire il caso che sia gia' creata // e sto andando in aggiornamento if( !tmpWorkDir.exists() ) { if( !tmpWorkDir.mkdirs() ) { batchJob.setMessage("Cant' creare working dir"); throw new EcmEngineException("ArchiveImporterJob: " +batchJob.getMessage() +" (" +tempPath +")"); } } // A questo punto, estraggo i file presenti nello zip String cFormat = pFormat.getValue(); logger.debug("[ArchiveImporterJob::execute] estrazione archivio (" +cFile +")(" +cFormat +") in "+tempPath); if (ECMENGINE_ARCHIVE_FORMAT_TAR.equals( cFormat )) { extractTar( new ByteArrayInputStream( content ), tempPath); } else if (ECMENGINE_ARCHIVE_FORMAT_TAR_GZ.equals( cFormat )) { extractTarGz( new ByteArrayInputStream( content ), tempPath); } else if (ECMENGINE_ARCHIVE_FORMAT_ZIP.equals( cFormat )) { extractZip( new ByteArrayInputStream( content ), tempPath); } else { // In caso di formato non gestito, esco con errore batchJob.setMessage("Format not supported"); throw new EcmEngineException("ArchiveImporterJob: " +batchJob.getMessage() +" (" +cFormat +")"); } // A questo punto, ho l'esplosione dello ZIP // Una volta esploso in modo corretto, cancello eventuali copie non previste // e rinomino il file oFileRenamed.delete(); oFile.renameTo( oFileRenamed ); // A fine processo, cancello i file interessati dalla import // Commentare questa riga se si volesse verificare come mai non viene importato un file oFile.delete(); oFileRenamed.delete(); } // Creo i service e verifico siano presi in modo corretto transactionService = (TransactionService)context.getJobDetail().getJobDataMap().get(ECMENGINE_TRANSACTION_SERVICE_BEAN); namespaceService = (NamespaceService)context.getJobDetail().getJobDataMap().get(ECMENGINE_NAMESPACE_SERVICE_BEAN); contentService = (ContentService)context.getJobDetail().getJobDataMap().get(ECMENGINE_CONTENT_SERVICE_BEAN); nodeService = (NodeService)context.getJobDetail().getJobDataMap().get(ECMENGINE_NODE_SERVICE_BEAN); authenticationService = (AuthenticationService)context.getJobDetail().getJobDataMap().get(ECMENGINE_AUTHENTICATION_SERVICE_BEAN); checkParam( batchJob, transactionService , "transactionService null" ); checkParam( batchJob, namespaceService , "namespaceService null" ); checkParam( batchJob, contentService , "contentService null" ); checkParam( batchJob, nodeService , "nodeService null" ); checkParam( batchJob, authenticationService , "authenticationService null" ); // Vengono presi i parametri del batch e ne viene controllata la conguenza, uscendo in caso di errore BatchJobParam pUID = batchJob.getParam(PARAM_UID ); BatchJobParam pStoreProtocol = batchJob.getParam(PARAM_STORE_PROTOCOL ); BatchJobParam pStoreIdentifier = batchJob.getParam(PARAM_STORE_IDENTIFIER ); BatchJobParam pUser = batchJob.getParam(PARAM_USER ); BatchJobParam pPassword = batchJob.getParam(PARAM_PASSWORD ); BatchJobParam pContentType = batchJob.getParam(PARAM_CONTENT_TYPE ); BatchJobParam pNameProperty = batchJob.getParam(PARAM_CONTENT_NAME_PROPERTY ); BatchJobParam pContainerType = batchJob.getParam(PARAM_CONTAINER_TYPE ); BatchJobParam pContainerNameProperty = batchJob.getParam(PARAM_CONTAINER_NAME_PROPERTY); BatchJobParam pContainerAssocType = batchJob.getParam(PARAM_CONTAINER_ASSOC_TYPE ); BatchJobParam pParentAssocType = batchJob.getParam(PARAM_PARENT_ASSOC_TYPE ); checkParam( batchJob, pUID , "Node UID not found" ); checkParam( batchJob, pStoreProtocol , "Store Protocol not found" ); checkParam( batchJob, pStoreIdentifier , "Store Identifier not found" ); checkParam( batchJob, pUser , "User not found" ); checkParam( batchJob, pPassword , "Password not found" ); checkParam( batchJob, pContentType , "Content Type not found" ); checkParam( batchJob, pNameProperty , "Content Name not found" ); checkParam( batchJob, pContainerType , "Container Type not found" ); checkParam( batchJob, pContainerNameProperty , "Container Name not found" ); checkParam( batchJob, pContainerAssocType , "Container Assoc not found" ); checkParam( batchJob, pParentAssocType , "Parent Assoc not found" ); // Trasformazione dei parametri in QName QName contentTypeQName = resolvePrefixNameToQName(pContentType .getValue() ); QName contentNamePropertyQName = resolvePrefixNameToQName(pNameProperty .getValue() ); QName containerTypeQName = resolvePrefixNameToQName(pContainerType .getValue() ); QName containerNamePropertyQName = resolvePrefixNameToQName(pContainerNameProperty .getValue() ); QName containerAssocTypeQName = resolvePrefixNameToQName(pContainerAssocType .getValue() ); QName parentAssocTypeQName = resolvePrefixNameToQName(pParentAssocType .getValue() ); // Prendo un oggetto UserTransaction UserTransaction transaction = transactionService.getNonPropagatingUserTransaction(); try { // Inizio la transazione transaction.begin(); // Cambio l'utente, con l'utente che ha deve importare authenticationService.authenticate( pUser.getValue() , EncryptionHelper.decrypt( pPassword.getValue() ).toCharArray() ); } catch(Exception e) { logger.debug( e ); throw e; }finally{ // Anche se non ho fatto try{transaction.rollback();}catch(Exception e){} } // Creo un nodo, usando l'UID del folder dove devo mettere i dati StoreRef sr = new StoreRef( pStoreProtocol.getValue(), pStoreIdentifier.getValue() ); // DictionarySvc.SPACES_STORE NodeRef nodeRef = new NodeRef( sr , pUID.getValue()); // Attivo l'importazione ricorsiva int nContent = handleRootFolder(tmpWorkDir , nodeRef , parentAssocTypeQName , containerTypeQName , containerNamePropertyQName , containerAssocTypeQName , contentTypeQName , contentNamePropertyQName); // Reimposto lo status e vado al prossimo JOB batchJob.setMessage("Content nuovi: " +nContent +" Datafile " +pName.getValue() +".processed"); batchJob.setStatus( JobStatus.FINISHED ); jobManager.updateJob(batchJob); } catch(Exception e) { logger.error("[ArchiveImporterJob::execute] ERROR", e); try { // Reimposto il getMessage(), nel caso arrivi vuoto if( batchJob.getMessage().length()==0 ){ batchJob.setMessage( e.getMessage() ); } // Reimposto lo status e vado al prossimo JOB batchJob.setStatus( JobStatus.ERROR ); jobManager.updateJob(batchJob); } catch(Exception ee) { // TODO: vedere se e' giusto tenerlo muto } } finally { // Non posso toccare lo stato a ready, altrimenti vado in loop } } } } } else { logger.error("[ArchiveImporterJob::execute] JobManager NULL per "+ECMENGINE_JOB_MANAGER_BEAN); } } catch(Exception e) { logger.error("[ArchiveImporterJob::execute] ERROR", e); throw new JobExecutionException(e); } finally { running = false; logger.debug("[ArchiveImporterJob::execute] END"); } logger.debug("[ArchiveImporterJob::execute] END run"); } /** * Metodo statico di utilità per la creazione del job da fornire come * parametro al job manager. * * @param archive * Un oggetto DataArchive con i dati dell'archivio da decomprimere * @param node * Il nodo dove importare i dti * @param context * L'istanza di {@code OperationContext} con la quale autenticarsi * @return L'istanza di {@code BatchJob} contenente i dati necessati al job * di gestione di un DataArchive * @throws Exception * @see {@link it.doqui.index.ecmengine.business.job.JobBusinessInterface} */ public static BatchJob createBatchJob( DataArchive archive, NodeRef node, OperationContext context ) throws Exception { BatchJob job = new BatchJob( ECMENGINE_ARCHIVE_IMPORTER_JOB_REF ); // UID sotto al quale agganciare o ZIP e suo store Ref String cUID = node.getId(); StoreRef sr = node.getStoreRef(); job.addParam(new BatchJobParam(PARAM_UID , cUID )); job.addParam(new BatchJobParam(PARAM_STORE_PROTOCOL , sr.getProtocol() )); job.addParam(new BatchJobParam(PARAM_STORE_IDENTIFIER , sr.getIdentifier() )); // Aggiungo l'utente che effettuera' l'operazione job.addParam(new BatchJobParam(PARAM_USER , context.getUsername() )); job.addParam(new BatchJobParam(PARAM_PASSWORD , EncryptionHelper.encrypt(context.getPassword()) )); // Serve creare un file temporaneo con dentro archive.getContent() e mettere il nome in un parametro String cTimeStamp = ""+new java.util.Date().getTime(); String cName = cTimeStamp +"_" +Math.random() +"_" +cUID +"." +archive.getFormat(); job.addParam(new BatchJobParam(PARAM_NAME , cName )); // Path di memorizzazione del file PARAM_NAME String workDir = TempFileProvider.getTempDir().getAbsolutePath(); job.addParam(new BatchJobParam(PARAM_CONTENTSTORE_DIR , workDir )); // Metto il content nel file temporaneo String cFile = workDir +File.separator +cName; putBinary( cFile, archive.getContent() ); // Formato del file da importare job.addParam(new BatchJobParam(PARAM_FORMAT , archive.getFormat())); // Parametri necessari all'esecuzione job.addParam(new BatchJobParam(PARAM_CONTENT_TYPE , getValue(archive.getMappedContentTypePrefixedName() , DEFAULT_CONTENT_TYPE ))); job.addParam(new BatchJobParam(PARAM_CONTENT_NAME_PROPERTY , getValue(archive.getMappedContentNamePropertyPrefixedName() , DEFAULT_CONTENT_NAME_PROPERTY ))); job.addParam(new BatchJobParam(PARAM_CONTAINER_TYPE , getValue(archive.getMappedContainerTypePrefixedName() , DEFAULT_CONTAINER_TYPE ))); job.addParam(new BatchJobParam(PARAM_CONTAINER_NAME_PROPERTY , getValue(archive.getMappedContainerNamePropertyPrefixedName() , DEFAULT_CONTAINER_NAME_PROPERTY ))); job.addParam(new BatchJobParam(PARAM_CONTAINER_ASSOC_TYPE , getValue(archive.getMappedContainerAssocTypePrefixedName() , DEFAULT_CONTAINER_ASSOC_TYPE ))); job.addParam(new BatchJobParam(PARAM_PARENT_ASSOC_TYPE , getValue(archive.getParentContainerAssocTypePrefixedName() , DEFAULT_PARENT_ASSOC_TYPE ))); return job; } /** * Metodo di comodità per validare un valore ed eventualmente utilizzare un * valore di default. * * <p> * Questo metodo verifica se {@code targetValue} è diverso da null o * stringa vuota e ne restituisce il valore. Altrimenti restituisce il * valore fornito come default. * </p> * * @param targetValue * Il valore da validare ed eventualmente restituire. * @param defaultValule * Il valore di default. * * @return {@code targetValue} se diverso da null o stringa vuota; * {@code defaultValue} altrimenti. */ static private String getValue(String targetValue, String defaultValue) { return (targetValue != null && targetValue.length() > 0) ? targetValue : defaultValue; } /** * Estrae il contenuto di un archivio in formato TAR. * * @param archiveInputStream L'input stream da cui leggere il contenuto dell'archivio. * @param path Il path della directory in cui estrarre il contenuto dell'archivio. * * @throws Exception */ private void extractTar(InputStream archiveInputStream, String path) throws Exception { logger.debug("[ArchiveImporterJob::extractTar] BEGIN"); TarInputStream inputStream = null; try { inputStream = new TarInputStream(archiveInputStream); String entryName = null; for (TarEntry entry = inputStream.getNextEntry(); entry != null; entry = inputStream.getNextEntry()) { entryName = entry.getName(); File entryFile = new File(path+File.separator+entryName); if (entry.isDirectory()) { if (!entryFile.mkdirs()) { throw new EcmEngineException("cannot create directory: "+entryFile.getAbsolutePath()); } } else { File parentDir = entryFile.getParentFile(); if (!parentDir.exists()) { if (!parentDir.mkdirs()) { throw new EcmEngineException("cannot create directory: "+parentDir.getAbsolutePath()); } } inputStream.copyEntryContents(new FileOutputStream(path+File.separator+entryName)); } } } catch(Exception e) { throw e; } finally { if (inputStream != null) { try { inputStream.close(); } catch(Exception ee) {} } logger.debug("[ArchiveImporterJob::extractTar] END"); } } /** * Estrae il contenuto di un archivio in formato TAR con compressione GZip. * * @param archiveInputStream L'input stream da cui leggere il contenuto dell'archivio. * @param path Il path della directory in cui estrarre il contenuto dell'archivio. * * @throws Exception */ private void extractTarGz(InputStream archiveInputStream, String path) throws Exception { logger.debug("[ArchiveImporterJob::extractTarGz] BEGIN"); try { extractTar(new GZIPInputStream(archiveInputStream), path); } catch(Exception e) { throw e; } finally { logger.debug("[ArchiveImporterJob::extractTarGz] END"); } } /** * Estrae il contenuto di un archivio in formato ZIP. * * @param archiveInputStream L'input stream da cui leggere il contenuto dell'archivio. * @param path Il path della directory in cui estrarre il contenuto dell'archivio. * * @throws Exception */ // TODO: verificare come mai se il file non e' ZIP non va in errore private void extractZip(InputStream archiveInputStream, String path) throws Exception { logger.debug("[ArchiveImporterJob::extractZip] BEGIN"); ZipInputStream inputStream = null; FileOutputStream fileOutputStream = null; try { inputStream = new ZipInputStream(archiveInputStream); String entryName = null; byte[] buffer = new byte[1024]; int n = 0; for (ZipEntry entry = inputStream.getNextEntry(); entry != null; entry = inputStream.getNextEntry()) { entryName = entry.getName(); File entryFile = new File(path+File.separator+entryName); if (entry.isDirectory()) { if (!entryFile.mkdirs()) { throw new EcmEngineException("cannot create directory: "+entryFile.getAbsolutePath()); } } else { File parentDir = entryFile.getParentFile(); if (!parentDir.exists()) { if (!parentDir.mkdirs()) { throw new EcmEngineException("cannot create directory: "+parentDir.getAbsolutePath()); } } fileOutputStream = new FileOutputStream(entryFile); while ((n=inputStream.read(buffer, 0, buffer.length)) > -1) { fileOutputStream.write(buffer, 0, n); } fileOutputStream.close(); } inputStream.closeEntry(); } } catch(Exception e) { throw e; } finally { if (inputStream != null) { try { inputStream.close(); } catch(Exception e) {} try { fileOutputStream.close(); } catch(Exception e) {} } logger.debug("[ArchiveImporterJob::extractZip] END"); } } private byte[] getBinary( String cFile ) throws Exception { File file = new File( cFile ); FileInputStream fileinputstream = new FileInputStream(file); byte abyte0[] = new byte[(int)file.length()]; fileinputstream.read(abyte0); fileinputstream.close(); return abyte0; } static private void putBinary( String cFile, byte[] bOut ) throws Exception { File file = new File( cFile ); FileOutputStream fileoutputstream = new FileOutputStream(file); fileoutputstream.write(bOut); fileoutputstream.close(); return; } private int handleRootFolder( File folder, NodeRef parentNodeRef, QName parentAssocTypeQName, QName containerTypeQName, QName containerNamePropertyQName, QName containerAssocTypeQName, QName contentTypeQName, QName contentNamePropertyQName) throws Exception { logger.debug("[ArchiveImporterJob::handleRootFolder] BEGIN"); // Conto quanti dati sono stati scritti int nContent = 0; try { // Prima si inizia col creare i singoli contenuti boolean bContent = false; { // Prendo un oggetto UserTransaction UserTransaction transaction = transactionService.getNonPropagatingUserTransaction(); try { // Inizio la transazione transaction.begin(); // Conto i content creati int nSubContent = 0; // Prima creo i content in una transazione File[] folderEntries = folder.listFiles(); for (File entry : folderEntries) { // Se e' una directory if( !entry.isDirectory() ){ logger.debug("[ArchiveImporterJob::handleRootFolder] creating content: "+entry.getName()+", nodeRef="+parentNodeRef+", association="+parentAssocTypeQName); // Creo il contenuti if( createContent(entry, parentNodeRef, contentTypeQName, contentNamePropertyQName, parentAssocTypeQName) ){ nSubContent++; } } } // Se ho inserito 0 content, e non si e' generata una eccezione, vuol dire che i dati inseriti // sono tutti dei doppioni, in questo caso, meto bContent a true, e lascio andare avanti l'algoritmo bContent = (nSubContent==0); nContent += nSubContent; logger.debug( "[ArchiveImporterJob::handleRootFolder] Content inseriti: " +nContent ); // Nel caso che si chiami una commit, senza righe da committare // C'e' una eccezione. // TODO: gestire le transazioni da 0 contenuti .. ma .. cosa fare in questa situazione? transaction.commit(); // Se non ho ecezione sulla commit, indico come true la creazione content bContent = true; logger.debug( "[ArchiveImporterJob::handleRootFolder] Content bool "+bContent ); } catch (RollbackException re) { try { transaction.rollback(); } catch(Exception ee) { logger.debug( "[ArchiveImporterJob::handleRootFolder] RollbackException" ); } } catch (EcmEngineFoundationException e) { // Rollback try { transaction.rollback(); } catch(Exception ee) { logger.debug( "[ArchiveImporterJob::handleRootFolder] EcmEngineFoundationException" ); } } catch(Exception e) { logger.debug( e ); throw e; } } // Se i contenuti vanno a buon fine, inizio a cancellarli da disco boolean bDelete = false; if( bContent ){ try { // Prima creo i content in una transazione File[] folderEntries = folder.listFiles(); for (File entry : folderEntries) { // Se e' una directory if( !entry.isDirectory() ){ // Cancello il contenuto entry.delete(); } } bDelete = true; } catch(Exception e) { logger.debug( e ); throw e; } } // Se le delete vanno a buon fine, inizio a creare le directory if( bDelete ){ try { boolean bDeleteFolder = true; // Per tutti i file della cartella File[] folderEntries = folder.listFiles(); for (File entry : folderEntries) { // Se e' una directory if (entry.isDirectory()) { // Create directory logger.debug("[ArchiveImporterJob::handleRootFolder] creating directory: "+entry.getName()+", nodeRef="+parentNodeRef+", association="+parentAssocTypeQName); // nodo di riferimento NodeRef nr = null; // Stranamente, per una get di dati, viene espressamente richiesta una transazione // Prendo un oggetto UserTransaction UserTransaction transaction = transactionService.getNonPropagatingUserTransaction(); try { // Inizio la transazione transaction.begin(); // Verifico se la cartella e' presente nel nodo padre nr = nodeService.getChildByName( parentNodeRef, parentAssocTypeQName, entry.getName() ); // Anche se non ho fatto transaction.rollback(); } catch(Exception e) { logger.debug( e ); throw e; }finally{ // Anche se non ho fatto try{transaction.rollback();}catch(Exception e){} } // Prendo un oggetto UserTransaction transaction = transactionService.getNonPropagatingUserTransaction(); boolean bTrans = false; try { // Se non e' presente, provo a crearla if( nr==null ){ bTrans = true; // Preparo le properties di un folder QName prefixedNameQName = resolvePrefixNameToQName("cm:"+entry.getName()); Map<QName,Serializable> props = new HashMap<QName, Serializable>(); props.put(containerNamePropertyQName, entry.getName()); // Inizio la transazione transaction.begin(); // Creo il folder ChildAssociationRef folderNodeRef = nodeService.createNode(parentNodeRef, parentAssocTypeQName, prefixedNameQName, containerTypeQName, props); // Nel caso che si chiami una commit, senza righe da committare // C'e' una eccezione. // TODO: gestire le transazioni da 0 contenuti transaction.commit(); nr = folderNodeRef.getChildRef(); } // Creazione del subfolder nContent += handleRootFolder( entry, nr, containerAssocTypeQName, // Non passo il parent, ma passo il containerAssocType nei folder figli containerTypeQName, containerNamePropertyQName, containerAssocTypeQName, contentTypeQName, contentNamePropertyQName); } catch (RollbackException re) { if( bTrans ){ try { transaction.rollback(); } catch(Exception ee) { logger.debug( re ); } } } catch (EcmEngineFoundationException e) { bDeleteFolder = false; // Rollback try { transaction.rollback(); } catch(Exception ee) { logger.debug( e ); } } catch(Exception e) { logger.debug( e ); throw e; } } } // Rimuovo la directory, se non ho avuto problemi rimuovendo le subdir if( bDeleteFolder ){ folder.delete(); } } catch(Exception e) { logger.debug( e ); throw e; } } } catch(Exception e) { logger.debug( e ); throw e; } finally { logger.debug("[ArchiveImporterJob::handleRootFolder] END"); } return nContent; } /** * Crea un contenuto sul repository a partire da un file su filesystem. * * @param content * Il file da creare sul repository. * @param parentNodeRef * Il {@code NodeRef} del nodo sotto cui creare il contenuto. * @param contentTypeQName * Il {@code QName} del tipo di contenuto da creare. * @param contentNamePropertyQName * Il {@code QName} del nome del contenuto da creare. * @param containerAssocTypeQName * Il {@code QName} dell'associazione che lega contenuto e nodo * padre. * * @return Il {@code NodeRef} del contenuto creato. * * @throws Exception */ private boolean createContent(File content, NodeRef parentNodeRef, QName contentTypeQName, QName contentNamePropertyQName, QName containerAssocTypeQName) throws Exception { logger.debug("[ArchiveImporterJob::createContent] BEGIN"); boolean bRet = false; try { // Verifico se il contenuto e' presente nel nodo padre NodeRef nr = nodeService.getChildByName( parentNodeRef, containerAssocTypeQName, content.getName() ); // Se non e' presente, provo a crearlo if( nr==null ){ // Creazione nodo QName prefixedNameQName = resolvePrefixNameToQName("cm:"+content.getName()); Map<QName,Serializable> props = new HashMap<QName, Serializable>(); props.put(contentNamePropertyQName, content.getName()); ChildAssociationRef contentChildRef = nodeService.createNode(parentNodeRef, containerAssocTypeQName, prefixedNameQName, contentTypeQName, props); // Scrittura contenuto final ContentWriter writer = contentService.getWriter(contentChildRef.getChildRef(), ContentModel.PROP_CONTENT, true); writer.setMimetype(new MimetypesFileTypeMap().getContentType(content)); writer.setEncoding(getEncoding(content)); writer.putContent(content); // Se riesco a scrivere bRet = true; } } catch(DuplicateChildNodeNameException dc){ // In caso di contenuto duplicato, viene usata una policy conservativa, e viene tenuto // Il valore presente in repository logger.debug( "[ArchiveImporterJob::createContent] Contenuto presente (" +content.getName() +")" ); } catch(Exception e) { throw e; } finally { logger.debug("[ArchiveImporterJob::createContent] END"); } return bRet; } /** * Restituisce l'encoding del file specificato. * * @param file Il file di cui ricavare l'encoding. * @return L'encoding del file specificato. */ private String getEncoding(File file) { String encoding = ""; try { FileReader fr = new FileReader(file); encoding = fr.getEncoding(); fr.close(); } catch (Exception e) {} return encoding; } private QName resolvePrefixNameToQName(String prefixName) { logger.debug("[ArchiveImporterJob::resolvePrefixNameToQName] BEGIN"); QName result = null; String [] nameParts = QName.splitPrefixedQName(prefixName); try { logger.debug("[ArchiveImporterJob::resolvePrefixNameToQName] Resolving to QName: " + prefixName); result = QName.createQName(nameParts[0], nameParts[1],namespaceService); logger.debug("[ArchiveImporterJob::resolvePrefixNameToQName] QName: " + result.toString()); } catch (RuntimeException e) { logger.debug("[ArchiveImporterJob::resolvePrefixNameToQName] " + "Error resolving to QName \"" + prefixName + "\": " + e.getMessage()); throw new RuntimeException(); // FIXME } finally { logger.debug("[ArchiveImporterJob::resolvePrefixNameToQName] END"); } return result; } private void checkParam( BatchJob batchJob, Object pParam, String cError ) throws EcmEngineException { if( pParam==null ){ batchJob.setMessage( cError ); throw new EcmEngineException("ArchiveImporterJob: " +batchJob.getMessage()); } } // http://www.rgagnon.com/javadetails/java-0064.html private boolean copyFile(File in, File out) { boolean bRet = false; FileChannel inChannel = null; FileChannel outChannel = null; try { inChannel = new FileInputStream(in).getChannel(); outChannel = new FileOutputStream(out).getChannel(); // On the Windows plateform, you can't copy a file bigger than 64Mb, an // Exception in thread "main" java.io.IOException: Insufficient system // resources exist to complete the requested service is thrown. // inChannel.transferTo(0, inChannel.size(), outChannel); // magic number for Windows, 64Mb - 32Kb) int maxCount = (64 * 1024 * 1024) - (32 * 1024); long size = inChannel.size(); long position = 0; while (position < size) { position += inChannel.transferTo(position, maxCount, outChannel); } bRet = true; } catch (IOException e) { //System.out.println( e ); //throw e; } finally { try { if (inChannel != null) inChannel.close(); } catch (IOException e) {} try { if (outChannel != null) outChannel.close();} catch (IOException e) {} } return bRet; } }