/******************************************************************************* * Copyright 2006 - 2014 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.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.Serializable; import java.util.HashMap; import java.util.Map; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.inject.Inject; import org.slf4j.Logger; import eu.scape_project.planning.utils.FileUtils; import eu.scape_project.planning.utils.OS; /** * A handler for loading and storing bytestreams of a (read: one) plan, it: - * uses a {@link IByteStreamStorage } for persisting the bytestreams permanently, * - and also takes care of caching bytestreams. Note: - It does NOT take any * measures to prevent concurrency issues,ensure thread savety. But this is ok, * because : - A plan cannot be loaded and accessed more than once at a time, * the ByteStreamManager shares the same (conversation) scope. - Beside * different planning-conversations we do not read/store bytestreams with * multiple threads. * * @author Michael Kraxner */ // FIXME: It would be nice to use one instance for the whole conversation, but // that is not possible due to ExperimentRunner's async call // @ConversationScoped public class ByteStreamManager implements Serializable, IByteStreamManager { private static final long serialVersionUID = 7205715730617180554L; @Inject private Logger log; @Inject private IByteStreamStorage storage; private Map<String, File> tempDigitalObjects = new HashMap<String, File>(); private File tempDir = null; /** * Constructs a new object. */ public ByteStreamManager() { } @Override public String store(String pid, byte[] bytestream) throws StorageException { if ("".equals(pid)) { pid = null; } pid = storage.store(pid, bytestream); // we have also to update the cache! try { cacheObject(pid, bytestream); } catch (IOException e) { throw new StorageException("failed to cache object", e); } return pid; } @Override public byte[] load(String pid) throws StorageException { // try to load it from the cache byte[] data = loadFromCache(pid); // if it is not in the cache load it from the storage and cache it if (data == null) { data = storage.load(pid); try { cacheObject(pid, data); } catch (IOException e) { throw new StorageException("failed to cache object", e); } } return data; } @Override public File getTempFile(String pid) { File tmp = tempDigitalObjects.get(pid); if (tmp == null) { try { load(pid); tmp = tempDigitalObjects.get(pid); } catch (StorageException e) { log.error("failed to retrieve object: " + pid, e); return null; } } return tmp; } @Override public void delete(String pid) throws StorageException { if ((pid == null) || (pid.isEmpty())) { return; } File f = tempDigitalObjects.remove(pid); if (f != null) { f.delete(); } storage.delete(pid); } /** * Stores the given bytestream to a tempfile and keeps the handle for later * use. * * @param pid * the pid of the object * @param bitstream * the object data * @throws IOException * if the object could not be stored */ private void cacheObject(String pid, byte[] bitstream) throws IOException { String filename = pid; String fileExtension = ""; int bodyEnd = filename.lastIndexOf("."); if (bodyEnd >= 0) { fileExtension = filename.substring(bodyEnd); } String tempFileName = tempDir.getAbsolutePath() + File.separator + System.nanoTime() + fileExtension; File tempFile = new File(tempFileName); OutputStream fileStream; fileStream = new BufferedOutputStream(new FileOutputStream(tempFile)); fileStream.write(bitstream); fileStream.close(); // queue file for deletion, in case the clean up fails tempFile.deleteOnExit(); // put the temp file in the map tempDigitalObjects.put(pid, tempFile); } /** * Loads the bytestream from the cache. * * @param pid * the pid of the object * @return the object data */ private byte[] loadFromCache(String pid) { File tmp = tempDigitalObjects.get(pid); if (tmp == null) { return null; } try { return FileUtils.inputStreamToBytes(new FileInputStream(tmp)); } catch (IOException e) { return null; } } /** * Creates a new temp directory for this ByteStreamManager. */ @PostConstruct public void init() { tempDir = new File(OS.getTmpPath() + "digitalobjects" + System.nanoTime() + File.separator); tempDir.mkdir(); tempDir.deleteOnExit(); } /** * Cleanup of tempfiles, and handles to loaded digital objects. */ @PreDestroy public void destroy() { OS.deleteDirectory(tempDir); tempDigitalObjects.clear(); } }