/* * Copyright (C) 2011 Jan Pokorsky * * 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 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package cz.cas.lib.proarc.common.imports; import cz.cas.lib.proarc.common.config.AppConfiguration; import cz.cas.lib.proarc.common.config.ConfigurationProfile; import cz.cas.lib.proarc.common.config.Profiles; import cz.cas.lib.proarc.common.dao.Batch; import cz.cas.lib.proarc.common.export.mets.JhoveContext; import cz.cas.lib.proarc.common.user.UserManager; import cz.cas.lib.proarc.common.user.UserProfile; import cz.cas.lib.proarc.common.user.UserUtil; import java.io.File; import java.io.IOException; import java.net.FileNameMap; import java.net.URLConnection; import java.util.Collections; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; /** * Import process * {@link #prepare(java.io.File, java.lang.String, cz.cas.lib.proarc.common.user.UserProfile, cz.cas.lib.proarc.common.imports.ImportBatchManager, java.lang.String, java.lang.String, boolean) checks} * preconditions of the import, * {@link #start() runs} the import and if necessary {@link #resume resumes} * already prepared import. * <p>The process delegates to {@link ImportHandler} that is bound to the profile. * * @author Jan Pokorsky */ public final class ImportProcess implements Runnable { private static final Logger LOG = Logger.getLogger(ImportProcess.class.getName()); static final String TMP_DIR_NAME = "proarc_import"; private final ImportBatchManager batchManager; private static List<TiffImporter> consumerRegistery; private final ImportOptions importConfig; ImportProcess(ImportOptions importConfig, ImportBatchManager batchManager) { this.importConfig = importConfig; this.batchManager = batchManager; } /** * Prepares a new import process. Creates batch, locks import folder. The process is ready * to run with {@link #start} immediately or later with {@link ImportDispatcher}. */ public static ImportProcess prepare( File importFolder, String description, UserProfile user, ImportBatchManager batchManager, String device, boolean generateIndices, ImportProfile profile ) throws IOException { ImportOptions options = new ImportOptions(importFolder, device, generateIndices, user, profile); ImportProcess process = new ImportProcess(options, batchManager); process.prepare(description, user); return process; } /** * Resumes a scheduled import process. * @see #prepare * @see ImportDispatcher */ public static ImportProcess resume(Batch batch, ImportBatchManager ibm, ImportProfile profile) { UserManager users = UserUtil.getDefaultManger(); UserProfile user = users.find(batch.getUserId()); File importFolder = ibm.resolveBatchFile(batch.getFolder()); ImportOptions options = ImportOptions.fromBatch( batch, importFolder, user, profile); // if necessary reset old computed batch items ImportProcess process = new ImportProcess(options, ibm); process.removeCaches(options.getImportFolder()); process.removeBatchItems(batch); return process; } /** * Read and submits scheduled processes from last session. * This should be run when application starts. */ public static void resumeAll(ImportBatchManager ibm, ImportDispatcher dispatcher, AppConfiguration config) { Profiles profiles = config.getProfiles(); List<Batch> batches2schedule = ibm.findLoadingBatches(); for (Batch batch : batches2schedule) { try { ConfigurationProfile profile = resolveProfile(batch, profiles); ImportProfile importCfg = config.getImportConfiguration(profile); ImportProcess resume = ImportProcess.resume(batch, ibm, importCfg); dispatcher.addImport(resume); } catch (Exception ex) { logBatchFailure(ibm, batch, ex); } } } private static ConfigurationProfile resolveProfile(Batch batch, Profiles profiles) { String profileId = batch.getProfileId(); if (profileId == null) { profileId = ConfigurationProfile.DEFAULT; } ConfigurationProfile profile = profiles.getProfile(ImportProfile.PROFILES, profileId); String err = null; if (profile == null) { err = String.format("Cannot resume batch %s! Missing profile %s." + " Check proarc.cfg and %s registrations.", batch.getId(), profileId, ImportProfile.PROFILES); } else if (profile.getError() != null) { err = String.format("Cannot resume batch %s! Check proarc.cfg.\n%s\nProfile error: %s", new Object[]{batch.getId(), profile, profile.getError()}); } if (err != null) { throw new IllegalStateException(err); } return profile; } private void prepare(String description, UserProfile user) throws IOException { // validate import folder File importFolder = importConfig.getImportFolder(); ImportFileScanner.validateImportFolder(importFolder); // check import state lockImportFolder(importFolder); boolean transactionFailed = true; try { if (getTargetFolder(importFolder).exists()) { throw new IOException("Folder already exists: " + getTargetFolder(importFolder)); } int estimateItemNumber = importConfig.getImporter().estimateItemNumber(importConfig); if (estimateItemNumber == 0) { throw new IOException("Nothing to import " + importFolder); } Batch batch = batchManager.add( importFolder, description, user, estimateItemNumber, importConfig); importConfig.setBatch(batch); transactionFailed = false; } finally { if (transactionFailed) { ImportFileScanner.rollback(importFolder); } } } private Batch logBatchFailure(Batch batch, Throwable t) { return logBatchFailure(batchManager, batch, t); } private static Batch logBatchFailure(ImportBatchManager batchManager, Batch batch, Throwable t) { LOG.log(Level.SEVERE, batch.toString(), t); batch.setState(Batch.State.LOADING_FAILED); batch.setLog(ImportBatchManager.toString(t)); return batchManager.update(batch); } @Override public void run() { start(); } /** * Starts the process. * @return the import batch */ public Batch start() { Batch batch = importConfig.getBatch(); if (batch == null) { throw new IllegalStateException("run prepare first!"); } File importFolder = importConfig.getImportFolder(); try { try { ImportFileScanner.validateImportFolder(importFolder); ImportFolderStatus folderStatus = batchManager.getFolderStatus(batch); if (folderStatus == null) { batchManager.updateFolderStatus(batch); } else if (!batch.getId().equals(folderStatus.getBatchId())) { throw new IllegalStateException(String.format( "The folder tracked by another batch import!\nfolder: %s\nfound ID: %s", importFolder, folderStatus.getBatchId())); } } catch (Throwable ex) { return logBatchFailure(batch, ex); } File targetFolder = createTargetFolder(importFolder); importConfig.setTargetFolder(targetFolder); importConfig.getImporter().start(importConfig); if (batch.getState() == Batch.State.LOADING) { batch.setState(Batch.State.LOADED); } batch = batchManager.update(batch); return batch; } catch (InterruptedException ex) { // rollback files on batch resume return null; } catch (Throwable t) { return logBatchFailure(batch, t); } } private void removeCaches(File importFoder) { deleteFolder(getTargetFolder(importFoder)); } private void removeBatchItems(Batch batch) { batchManager.resetBatch(batch); } public ImportOptions getImportConfig() { return importConfig; } public Batch getBatch() { return importConfig.getBatch(); } static File createTargetFolder(File importFolder) throws IOException { File folder = getTargetFolder(importFolder); if (!folder.mkdir()) { throw new IOException("Import folder already exists: " + folder); } return folder; } static File getTargetFolder(File importFolder) { File folder = new File(importFolder, TMP_DIR_NAME); return folder; } private void lockImportFolder(File folder) throws IOException { File statusFile = new File(folder, ImportFileScanner.IMPORT_STATE_FILENAME); if (!statusFile.createNewFile()) { throw new IOException("Folder already imported: " + folder); } } private static void deleteFolder(File folder) { if (folder.exists()) { for (File f : folder.listFiles()) { if (f.isDirectory()) { deleteFolder(f); } else { f.delete(); } } folder.delete(); } } static List<TiffImporter> getConsumers() { if (consumerRegistery == null) { consumerRegistery = Collections.singletonList( new TiffImporter(ImportBatchManager.getInstance())); } return consumerRegistery; } /** * Simplified version uses filename extension. For niftier alternatives see * http://www.rgagnon.com/javadetails/java-0487.html */ public static String findMimeType(File f) { FileNameMap fileNameMap = URLConnection.getFileNameMap(); return fileNameMap.getContentTypeFor(f.getName()); } public static final class ImportOptions { /** Folder with origin scan. */ private final File importFolder; /** Folder containing generated stuff. It is available when the import * starts, not in prepare state. */ private File targetFolder; private String device; private boolean generateIndices; private int consumedFileCounter; private final UserProfile user; private Batch batch; private final ImportProfile profile; private JhoveContext jhoveContext; private ImportHandler importer; ImportOptions(File importFolder, String device, boolean generateIndices, UserProfile username, ImportProfile profile ) { this.device = device; this.generateIndices = generateIndices; this.user = username; this.importFolder = importFolder; this.profile = profile; } public ImportHandler getImporter() { if (importer == null) { importer = profile.createImporter(); } return importer; } public File getImportFolder() { return importFolder; } public File getTargetFolder() { return targetFolder; } public void setTargetFolder(File targetFolder) { this.targetFolder = targetFolder; } public boolean isGenerateIndices() { return generateIndices; } public String getDevice() { return device; } public String getModel() { return profile.getModelId(); } public int getConsumedFileCounter() { return consumedFileCounter; } public void setConsumedFileCounter(int consumedFileCounter) { this.consumedFileCounter = consumedFileCounter; } public String getUsername() { return user != null ? user.getUserName() : null; } public UserProfile getUser() { return user; } public Batch getBatch() { return batch; } public void setBatch(Batch batch) { this.batch = batch; } public ImportProfile getConfig() { return profile; } public JhoveContext getJhoveContext() { return jhoveContext; } public void setJhoveContext(JhoveContext jhoveContext) { this.jhoveContext = jhoveContext; } public static ImportOptions fromBatch(Batch batch, File importFolder, UserProfile username, ImportProfile profile) { ImportOptions options = new ImportOptions( importFolder, batch.getDevice(), batch.isGenerateIndices(), username, profile); options.setBatch(batch); return options; } } }