/******************************************************************************* * Copyright 2006 - 2012 Vienna University of Technology, * Department of Software Technology and Interactive Systems, IFS * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package eu.scape_project.planning.manager; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.Serializable; import java.util.UUID; import javax.annotation.PostConstruct; import javax.ejb.Stateful; import javax.inject.Inject; import org.apache.commons.configuration.Configuration; import org.slf4j.Logger; import eu.scape_project.planning.utils.ConfigurationLoader; import eu.scape_project.planning.utils.FileUtils; /** * A {@link IByteStreamStorage} which stores the data in the file system. The * location of the files is defined by {@link #storagePath} * * Note: - atm this storage handler shares the same scope as the plan being * worked on - it is not defined what happens if multiple threads store data for * the same pid This is not be a problem, as a bytestream is always related to * one plan(which can only be accessed once at a time) and we do not have * multiple threads altering the same bytestream * * @author Michael Kraxner */ // Do not use @ConversationScoped because the ExperimentRunner is called // asynchronous and no ConversationScope is available then. @Stateful //@SessionScoped public class FileStorage implements Serializable, IByteStreamStorage { private static final long serialVersionUID = -2406172386311143101L; @Inject private Logger log; /** * The storage path. */ private String storagePath = null; /** * File handle to storagePath. */ private File storagePathFile; /** * will be used as namespace for persistent identifiers, according to * {@link https://wiki.duraspace.org/display/FEDORA35/Fedora+Identifiers}. */ private String repositoryName; /** * Default constructor. */ public FileStorage() { } /** * Initializes class. */ @PostConstruct public void init() { ConfigurationLoader configurationLoader = new ConfigurationLoader(); Configuration config = configurationLoader.load(); storagePath = config.getString("filestorage.path"); if (storagePath != null) { storagePathFile = new File(storagePath); if (!storagePathFile.exists()) { if (storagePathFile.mkdirs()) { log.info("Storage path created and set to {}.", storagePathFile.getAbsoluteFile()); } else { log.error("Storage path could not be created."); } } else { log.info("Storage path set to {}.", storagePathFile.getAbsoluteFile()); } } else { log.error("Storage path not set."); } repositoryName = config.getString("filestorage.repository.name"); if (repositoryName == null) { log.error("Repository name not set."); } } @Override public String store(String pid, byte[] bytestream) throws StorageException { String objectId; if (pid == null) { // a new object objectId = UUID.randomUUID().toString(); pid = repositoryName + ":" + objectId; } else { // we ignore the object's namespace objectId = pid.substring(pid.indexOf(':') + 1); } // we try to rename the file, if it already exists File file = new File(storagePathFile, objectId); File backup = null; if (file.exists()) { try { backup = File.createTempFile(file.getName(), "backup", storagePathFile); file.renameTo(backup); } catch (IOException e) { throw new StorageException("failed to create backup for: " + pid, e); } } try { // write data to filesystem FileUtils.writeToFile(new ByteArrayInputStream(bytestream), new FileOutputStream(file)); // data was stored successfully, backup is not needed any more if (backup != null) { backup.delete(); } } catch (IOException e) { // try to restore old file if (backup != null) { if (backup.renameTo(file)) { backup = null; } else { throw new StorageException("failed to store digital object: " + pid + " and failed to restore backup!"); } } throw new StorageException("failed to store digital object: " + pid, e); } return pid; } @Override public byte[] load(String pid) throws StorageException { File file = getFile(pid); try { return FileUtils.inputStreamToBytes(new FileInputStream(file)); } catch (IOException e) { throw new StorageException("failed to load data for persistent identifier: " + pid); } } @Override public void delete(String pid) throws StorageException { File file = getFile(pid); if (!file.delete()) { log.error("failed to delete object: " + pid); } } /** * Returns a file for the provided pid. * * @param pid * the pid of the object * @return the file * @throws StorageException * if the pid is empty or the object could not be found */ private File getFile(String pid) throws StorageException { if ((pid == null) || (pid.isEmpty())) { throw new StorageException("provided persistent identifier is empty"); } String objectId = pid.substring(pid.indexOf(':') + 1); File file = new File(storagePathFile, objectId); if (file.exists()) { return file; } else { throw new StorageException("no object found for persistent identifier: " + pid); } } // --------------- getter/setter --------------- public String getStoragePath() { return storagePath; } }