001/*
002 * #%L
003 * Netarchivesuite - common - test
004 * %%
005 * Copyright (C) 2005 - 2014 The Royal Danish Library, the Danish State and University Library,
006 *             the National Library of France and the Austrian National Library.
007 * %%
008 * This program is free software: you can redistribute it and/or modify
009 * it under the terms of the GNU Lesser General Public License as
010 * published by the Free Software Foundation, either version 2.1 of the
011 * License, or (at your option) any later version.
012 * 
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016 * GNU General Lesser Public License for more details.
017 * 
018 * You should have received a copy of the GNU General Lesser Public
019 * License along with this program.  If not, see
020 * <http://www.gnu.org/licenses/lgpl-2.1.html>.
021 * #L%
022 */
023
024package dk.netarkivet.common.arcrepository;
025
026import java.io.File;
027import java.io.FileOutputStream;
028import java.io.FilenameFilter;
029import java.io.IOException;
030import java.io.OutputStream;
031import java.util.regex.Pattern;
032
033import org.archive.io.ArchiveReader;
034import org.archive.io.ArchiveReaderFactory;
035import org.archive.io.ArchiveRecord;
036import org.slf4j.Logger;
037import org.slf4j.LoggerFactory;
038
039import dk.netarkivet.common.distribute.RemoteFileFactory;
040import dk.netarkivet.common.distribute.arcrepository.ArcRepositoryClient;
041import dk.netarkivet.common.distribute.arcrepository.BatchStatus;
042import dk.netarkivet.common.distribute.arcrepository.BitarchiveRecord;
043import dk.netarkivet.common.distribute.arcrepository.Replica;
044import dk.netarkivet.common.distribute.arcrepository.ReplicaStoreState;
045import dk.netarkivet.common.exceptions.ArgumentNotValid;
046import dk.netarkivet.common.exceptions.IOFailure;
047import dk.netarkivet.common.exceptions.NotImplementedException;
048import dk.netarkivet.common.utils.FileUtils;
049import dk.netarkivet.common.utils.batch.BatchLocalFiles;
050import dk.netarkivet.common.utils.batch.FileBatchJob;
051
052/**
053 * A minimal implementation of ArcRepositoryClient that just has one local directory that it keeps its files in, no
054 * checking no nothing.
055 */
056public class TrivialArcRepositoryClient implements ArcRepositoryClient {
057    /** The directory name of the local arcrepository. */
058    private static final String ARC_REPOSITORY_DIR_NAME = "ArcRepository";
059    /** Store files in this dir -- might later use a separate setting. */
060    private final File dir = new File(FileUtils.getTempDir(), ARC_REPOSITORY_DIR_NAME);
061    /** The class logger. */
062    //private Log log = LogFactory.getLog(getClass());
063    private static final Logger log = LoggerFactory.getLogger(TrivialArcRepositoryClient.class);
064
065    /**
066     * Constructor for this class. Creates a local directory for the arcrepository.
067     */
068    public TrivialArcRepositoryClient() {
069        FileUtils.createDir(dir);
070    }
071
072    /** Call on shutdown to release external resources. */
073    public void close() {
074    }
075
076    /**
077     * Store the given file in the ArcRepository. After storing, the file is deleted.
078     *
079     * @param file A file to be stored. Must exist.
080     * @throws IOFailure thrown if store is unsuccessful, or failed to clean up files after the store operation.
081     * @throws ArgumentNotValid if file parameter is null or file is not an existing file.
082     */
083    public void store(File file) throws IOFailure, ArgumentNotValid {
084        ArgumentNotValid.checkNotNull(file, "file");
085        FileUtils.copyFile(file, new File(dir, file.getName()));
086        FileUtils.remove(file);
087    }
088
089    /**
090     * Gets a single ARC record out of the ArcRepository.
091     *
092     * @param arcfile The name of a file containing the desired record.
093     * @param index The offset of the desired record in the file
094     * @return a BitarchiveRecord-object, or null if request times out or object is not found.
095     * @throws ArgumentNotValid if arcfile is null or empty, or index is negative
096     * @throws IOFailure If the get operation failed.
097     */
098    public BitarchiveRecord get(String arcfile, long index) throws ArgumentNotValid {
099        ArgumentNotValid.checkNotNullOrEmpty(arcfile, "arcfile");
100        ArgumentNotValid.checkNotNegative(index, "index");
101        ArchiveReader reader = null;
102        ArchiveRecord record = null;
103        try {
104            reader = ArchiveReaderFactory.get(new File(dir, arcfile), index);
105            record = reader.get();
106            return new BitarchiveRecord(record, arcfile);
107        } catch (IOException e) {
108            throw new IOFailure("Error reading record from '" + arcfile + "' offset " + index, e);
109        } finally {
110            if (record != null) {
111                try {
112                    record.close();
113                } catch (IOException e) {
114                    log.info("Error closing ARC record '" + record + "'", e);
115                }
116            }
117            if (reader != null) {
118                try {
119                    reader.close();
120                } catch (IOException e) {
121                    log.info("Error closing ARC reader '" + reader + "'", e);
122                }
123            }
124        }
125    }
126
127    /**
128     * Retrieves a file from an ArcRepository and places it in a local file.
129     *
130     * @param arcfilename Name of the arcfile to retrieve.
131     * @param replica The bitarchive to retrieve the data from (not used in this implementation)
132     * @param toFile Filename of a place where the file fetched can be put.
133     * @throws IOFailure if there are problems getting a reply or the file could not be found.
134     */
135    public void getFile(String arcfilename, Replica replica, File toFile) {
136        ArgumentNotValid.checkNotNullOrEmpty(arcfilename, "arcfilename");
137        ArgumentNotValid.checkNotNull(toFile, "toFile");
138        FileUtils.copyFile(new File(dir, arcfilename), toFile);
139    }
140
141    /**
142     * Runs a batch batch job on each file in the ArcRepository.
143     *
144     * @param job An object that implements the FileBatchJob interface. The initialize() method will be called before
145     * processing and the finish() method will be called afterwards. The process() method will be called with each File
146     * entry. An optional function postProcess() allows handling the combined results of the batchjob, e.g. summing the
147     * results, sorting, etc.
148     * @param replicaId The archive to execute the job on (not used in this implementation)
149     * @param args The arguments for the batchjob.
150     * @return The status of the batch job after it ended.
151     */
152    public BatchStatus batch(final FileBatchJob job, String replicaId, String... args) {
153        ArgumentNotValid.checkNotNull(job, "job");
154        OutputStream os = null;
155        File resultFile;
156        try {
157            resultFile = File.createTempFile("batch", replicaId, FileUtils.getTempDir());
158            os = new FileOutputStream(resultFile);
159            File[] files = dir.listFiles(new FilenameFilter() {
160                public boolean accept(File dir, String name) {
161                    Pattern filenamePattern = job.getFilenamePattern();
162                    return new File(dir, name).isFile()
163                            && (filenamePattern == null || filenamePattern.matcher(name).matches());
164                }
165            });
166            BatchLocalFiles batcher = new BatchLocalFiles(files);
167            batcher.run(job, os);
168        } catch (IOException e) {
169            throw new IOFailure("Cannot perform batch job '" + job + "'", e);
170        } finally {
171            if (os != null) {
172                try {
173                    os.close();
174                } catch (IOException e) {
175                    log.info("Error closing batch output stream '" + os + "'", e);
176                }
177            }
178        }
179        return new BatchStatus(replicaId, job.getFilesFailed(), job.getNoOfFilesProcessed(),
180                RemoteFileFactory.getMovefileInstance(resultFile), job.getExceptions());
181    }
182
183    /**
184     * Updates the administrative data in the ArcRepository for a given file and replica. (not implemented)
185     *
186     * @param fileName The name of a file stored in the ArcRepository.
187     * @param bitarchiveId The id of the replica that the administrative data for fileName is wrong for.
188     * @param newval What the administrative data will be updated to.
189     */
190    public void updateAdminData(String fileName, String bitarchiveId, ReplicaStoreState newval) {
191        throw new NotImplementedException("Function has not been implemented");
192    }
193
194    /**
195     * Updates the checksum kept in the ArcRepository for a given file. It is the responsibility of the ArcRepository
196     * implementation to ensure that this checksum matches that of the underlying files.
197     *
198     * @param filename The name of a file stored in the ArcRepository.
199     * @param checksum The new checksum.
200     */
201    public void updateAdminChecksum(String filename, String checksum) {
202        throw new NotImplementedException("Function has not been implemented");
203    }
204
205    /**
206     * Remove a file from one part of the ArcRepository, retrieving a copy for security purposes. This is typically used
207     * when repairing a file that has been corrupted.
208     *
209     * @param fileName The name of the file to remove.
210     * @param bitarchiveId The id of the replica from which to remove the file (not used)
211     * @param checksum The checksum of the file to be removed (not used)
212     * @param credentials A string that shows that the user is allowed to perform this operation (not used)
213     * @return A local copy of the file removed.
214     */
215    public File removeAndGetFile(String fileName, String bitarchiveId, String checksum, String credentials) {
216        ArgumentNotValid.checkNotNullOrEmpty(fileName, "fileName");
217        // Ignores bitarchiveId, checksum, and credentials for now
218        File copiedTo = null;
219        try {
220            copiedTo = File.createTempFile("removeAndGetFile", fileName);
221        } catch (IOException e) {
222            throw new IOFailure("Cannot make temp file to copy '" + fileName + "' into", e);
223        }
224        File file = new File(dir, fileName);
225        FileUtils.copyFile(file, copiedTo);
226        FileUtils.remove(file);
227        return copiedTo;
228    }
229
230    public File getAllChecksums(String replicaId) {
231        // TODO Auto-generated method stub
232        throw new NotImplementedException("TODO: Implement me!");
233    }
234
235    public File getAllFilenames(String replicaId) {
236        // TODO Auto-generated method stub
237        throw new NotImplementedException("TODO: Implement me!");
238    }
239
240    public File correct(String replicaId, String checksum, File file, String credentials) {
241        // TODO Auto-generated method stub
242        throw new NotImplementedException("TODO: Implement me!");
243    }
244
245    @Override
246    public String getChecksum(String replicaId, String filename) {
247        // TODO Auto-generated method stub
248        return null;
249    }
250}