/* * Copyright (c) 2008 Los Alamos National Security, LLC. * * Los Alamos National Laboratory * Research Library * Digital Library Research & Prototyping Team * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ package gov.lanl.adore.djatoka.openurl; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.net.URI; import java.net.URL; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; import java.util.Properties; import javax.servlet.http.HttpServletResponse; import org.apache.log4j.Logger; import gov.lanl.adore.djatoka.util.ImageRecord; import info.openurl.oom.entities.Referent; /** * Default IReferentResolver implementation that uses a tab delimited file to * define the association between rft_id and file path. The imgIndexFile should * be defined in the OpenURLJP2KService.properties file as SimpleListResolver.imgIndexFile. * * Property: SimpleListResolver.imgIndexFile=imgIndex.txt * Format: rft_id\tfile_path * * @author Ryan Chute * */ public class SimpleListResolver implements IReferentResolver { static Logger logger = Logger.getLogger(SimpleListResolver.class); private static final String PROP_IMGS_INDEX = "SimpleListResolver.imgIndexFile"; private static final String PROP_REMOTE_CACHE = "SimpleListResolver.maxRemoteCacheSize"; private static final int DEFAULT_REMOTE_CACHE_SIZE = 100; private static int maxRemoteCacheSize = DEFAULT_REMOTE_CACHE_SIZE; private static Map<String, ImageRecord> imgs; private static IReferentMigrator dim = new DjatokaImageMigrator(); // Keep track of downloaded images, delete when maxCache is hit private static LinkedHashMap<String, String> remoteCacheMap; /** * Referent Identifier to be resolved from Identifier Resolver. The returned * ImageRecord need only contain the imageId and image file path. * @param rft identifier of the image to be resolved * @return ImageRecord instance containing resolvable metadata * @throws ResolverException */ public ImageRecord getImageRecord(Referent rft) throws ResolverException { String id = ((URI) rft.getDescriptors()[0]).toASCIIString(); return getImageRecord(id); } /** * Referent Identifier to be resolved from Identifier Resolver. The returned * ImageRecord need only contain the imageId and image file path. * @param rftId identifier of the image to be resolved * @return ImageRecord instance containing resolvable metadata * @throws ResolverException */ public ImageRecord getImageRecord(String rftId) throws ResolverException { ImageRecord ir = imgs.get(rftId); if (ir == null && isResolvableURI(rftId)) { try { URI uri = new URI(rftId); if (dim.getProcessingList().contains(uri.toString())) { int i = 0; Thread.sleep(1000); while (dim.getProcessingList().contains(uri) && i < (5 * 60)){ Thread.sleep(1000); i++; } if (imgs.containsKey(rftId)) return imgs.get(rftId); } File f = dim.convert(uri); ir = new ImageRecord(rftId, f.getAbsolutePath()); // LRU cache will delete oldest file when max is reached, // will also remove object from imgs and remoteCacheMap remoteCacheMap.put(rftId, f.getAbsolutePath()); if (f.length() > 0) imgs.put(rftId, ir); else throw new ResolverException("An error occurred processing file:" + uri.toURL().toString()); } catch (Exception e) { logger.error(e,e); throw new ResolverException(e); } } else if (isResolvableURI(rftId) && !new File(ir.getImageFile()).exists()) { // Handle ImageRecord in cache, but file does not exist on the file system imgs.remove(rftId); remoteCacheMap.remove(rftId); return getImageRecord(rftId); } return ir; } private static boolean isResolvableURI(String rftId) { return (rftId.startsWith("http") || rftId.startsWith("file") || rftId.startsWith("ftp")); } /** * Returns list of most recently requested images in accessed order. * @param cnt limit list to top n ImageRecords * @return list of requested image records */ public ArrayList<ImageRecord> getImageRecordList(int cnt) { if (cnt >= imgs.size()) return new ArrayList<ImageRecord>(imgs.values()); else { ArrayList<ImageRecord> l = new ArrayList<ImageRecord>(); int i = 0; for (ImageRecord rec : imgs.values()) { if (rec != null && i < cnt) { l.add(rec); i++; } else return l; } } return null; } /** * Sets a Properties object that may be used by underlying implementation * @param props Properties object for use by implementation * @throws ResolverException */ public void setProperties(Properties props) throws ResolverException { try { String prop = props.getProperty(PROP_IMGS_INDEX); if (prop != null) { URL url = Thread.currentThread().getContextClassLoader().getResource(props.getProperty(PROP_IMGS_INDEX)); imgs = getRecordMap(url.getFile()); } else throw new ResolverException(PROP_IMGS_INDEX + " is not defined."); // Initialize remote image cache management String mrcs = props.getProperty(PROP_REMOTE_CACHE); if (mrcs != null) maxRemoteCacheSize = Integer.parseInt(mrcs); remoteCacheMap = new LinkedHashMap<String, String>(16, .85f, true) { private static final long serialVersionUID = 1; protected boolean removeEldestEntry(Map.Entry<String, String> eldest) { logger.debug("remoteCacheSize: " + size()); boolean d = size() > maxRemoteCacheSize; if (d) { File f = new File((String) eldest.getValue()); logger.debug("deleting: " + eldest.getValue()); if (f.exists()) f.delete(); remove(eldest.getKey()); imgs.remove(eldest.getKey()); } return false; }; }; } catch (Exception e) { logger.error(e,e); throw new ResolverException(e); } } public IReferentMigrator getReferentMigrator() { return dim; } public int getStatus(String rftId) { if (imgs.get(rftId) != null) return HttpServletResponse.SC_OK; else if (dim.getProcessingList().contains(rftId)) return HttpServletResponse.SC_ACCEPTED; else return HttpServletResponse.SC_NOT_FOUND; } private static Map<String, ImageRecord> getRecordMap(String f) throws Exception { Map<String, ImageRecord> map = Collections.synchronizedMap(new LinkedHashMap<String, ImageRecord>(16, 0.75f, true)); BufferedReader reader = new BufferedReader(new FileReader(f)); String row = null; for (int line = 0; true; line++) { row = reader.readLine(); if (row == null) break; String[] v = row.split("\t"); if (v.length < 2) System.out.println("Invalid format for Record Map; expects tab delimited id\tfilepath"); ImageRecord r = new ImageRecord(v[0], v[1]); map.put(v[0], r); } return map; } }