package info.freelibrary.djatoka.view; import gov.lanl.adore.djatoka.openurl.DjatokaImageMigrator; import gov.lanl.adore.djatoka.openurl.IReferentMigrator; import gov.lanl.adore.djatoka.openurl.IReferentResolver; import gov.lanl.adore.djatoka.openurl.ResolverException; import gov.lanl.adore.djatoka.util.ImageRecord; import gov.lanl.util.DBCPUtils; import info.freelibrary.djatoka.Constants; import info.freelibrary.util.FileUtils; import info.freelibrary.util.PairtreeObject; import info.freelibrary.util.PairtreeRoot; import info.freelibrary.util.PairtreeUtils; import info.freelibrary.util.RegexFileFilter; import info.freelibrary.util.StringUtils; import info.openurl.oom.entities.Referent; import java.io.File; import java.io.FileNotFoundException; import java.io.FilenameFilter; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URLDecoder; import java.net.URLEncoder; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Map; import java.util.Properties; import java.util.concurrent.ConcurrentHashMap; import javax.servlet.http.HttpServletResponse; import javax.sql.DataSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /* * This class needs to be redone... remove the database stuff and make the file * system vs. the Pairtree file system pulls cleaner... */ public class IdentifierResolver implements IReferentResolver, Constants { private static final Logger LOGGER = LoggerFactory .getLogger(IdentifierResolver.class); public static final String DEFAULT_DBID = IdentifierResolver.class .getSimpleName(); public static final String FIELD_IDENTIFIER = "identifier"; public static final String FIELD_IMAGEFILE = "imageFile"; public static final String REPLACE_ID_KEY = "\\i"; public static String myQuery = "SELECT identifier, imageFile FROM resources WHERE identifier='\\i';"; private static final String CHECK_DATABASE_CONFIG = DEFAULT_DBID + ".checkDatabase"; private IReferentMigrator myMigrator = new DjatokaImageMigrator(); private Map<String, ImageRecord> myRemoteImages; private Map<String, ImageRecord> myLocalImages; private DataSource myDataSource; private boolean myDatabaseIsActive; private String myJP2Dir; public ImageRecord getImageRecord(String aReferentID) throws ResolverException { ImageRecord image = getCachedImage(aReferentID); if (image == null && isResolvableURI(aReferentID)) { image = getRemoteImage(aReferentID); } return image; } public ImageRecord getImageRecord(Referent aReferent) throws ResolverException { String id = ((URI) aReferent.getDescriptors()[0]).toASCIIString(); return getImageRecord(id); } public IReferentMigrator getReferentMigrator() { return myMigrator; } public int getStatus(String aReferentID) { if (myRemoteImages.get(aReferentID) != null || getCachedImage(aReferentID) != null) { return HttpServletResponse.SC_OK; } else if (myMigrator.getProcessingList().contains(aReferentID)) { return HttpServletResponse.SC_ACCEPTED; } else { return HttpServletResponse.SC_NOT_FOUND; } } public void setProperties(Properties aProps) throws ResolverException { String checkDB = aProps.getProperty(CHECK_DATABASE_CONFIG); String query = aProps.getProperty(DEFAULT_DBID + ".query"); String prodInstance = aProps.getProperty("djatoka.ignore.fscache"); boolean skipFS = Boolean.parseBoolean(prodInstance); myJP2Dir = aProps.getProperty(JP2_DATA_DIR); // We can choose to use the database or the local file system cache if (checkDB != null) { myDatabaseIsActive = Boolean.parseBoolean(checkDB); if (LOGGER.isDebugEnabled() && myDatabaseIsActive) { LOGGER.debug("Database connection is configured"); } } myLocalImages = new ConcurrentHashMap<String, ImageRecord>(); myRemoteImages = new ConcurrentHashMap<String, ImageRecord>(); try { if (!skipFS) { loadFileSystemImages(myJP2Dir); } else if (LOGGER.isDebugEnabled()) { LOGGER.debug("File system mapping disabled"); } } catch (FileNotFoundException details) { if (LOGGER.isWarnEnabled()) { LOGGER.warn("{} couldn't be found", myJP2Dir); } } try { if (myDatabaseIsActive) { if (query == null) { if (LOGGER.isDebugEnabled()) { LOGGER.debug( "{}.query is not defined in properties file (using: {})", DEFAULT_DBID, myQuery); } } else { myQuery = query; } myDataSource = DBCPUtils.setupDataSource(DEFAULT_DBID, aProps); } } catch (Throwable details) { LOGGER.error(details.getMessage(), details); throw new ResolverException( "DBCP Libraries are not in the classpath"); } } public void loadFileSystemImages(String aJP2DataDir) throws FileNotFoundException { File jp2Dir = new File(aJP2DataDir); FilenameFilter filter = new RegexFileFilter(JP2_FILE_PATTERN); String[] skipped = new String[] { "pairtree_root" }; // Descend through file system but skipped our ID mapped PT directory for (File file : FileUtils.listFiles(jp2Dir, filter, true, skipped)) { ImageRecord image = new ImageRecord(); String id = stripExt(file.getName()); try { id = URLEncoder.encode(id, "UTF-8"); } catch (UnsupportedEncodingException details) { // Should be impossible to get here, UTF-8 is always supported throw new RuntimeException(details); } if (LOGGER.isDebugEnabled()) { LOGGER.debug("Loading {} ({})", id, file); } image.setIdentifier(id); image.setImageFile(file.getAbsolutePath()); if (myLocalImages == null) { myLocalImages = new ConcurrentHashMap<String, ImageRecord>(); myRemoteImages = new ConcurrentHashMap<String, ImageRecord>(); } myLocalImages.put(id, image); } } private boolean isResolvableURI(String aReferentID) { return aReferentID.startsWith("http"); // keeping it simple } // Not sure we should do this, but... private String stripExt(String aFileName) { int index = aFileName.lastIndexOf('.'); return index != -1 ? aFileName.substring(0, index) : aFileName; } private ImageRecord getCachedImage(String aReferentID) { ImageRecord image = null; if (!myDatabaseIsActive) { // First try cache of files loaded from file system // This is used for the simple file system viewer image = myLocalImages.get(aReferentID); if (LOGGER.isDebugEnabled() && image != null) { LOGGER.debug("{} found in the local cache", aReferentID); } else if (image == null) { // Try loading from our PairTree FS try { PairtreeRoot pairtree = new PairtreeRoot(new File(myJP2Dir)); String id = URLDecoder.decode(aReferentID, "UTF-8"); PairtreeObject dir = pairtree.getObject(id); String filename = PairtreeUtils.encodeID(id); File file = new File(dir, filename); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Checking in Pairtree cache: {}", file); } if (file.exists()) { image = new ImageRecord(); image.setIdentifier(id); image.setImageFile(file.getAbsolutePath()); } } catch (IOException details) { LOGGER.error("Failed to load file from cache", details); } } } else { Connection conn = null; Statement stmt = null; ResultSet rset = null; try { conn = myDataSource.getConnection(); stmt = conn.createStatement(); rset = stmt.executeQuery(myQuery.replace(REPLACE_ID_KEY, aReferentID)); if (rset.next()) { image = new ImageRecord(); image.setIdentifier(rset.getString(FIELD_IDENTIFIER)); image.setImageFile(rset.getString(FIELD_IMAGEFILE)); } } catch (SQLException details) { LOGGER.error(details.getMessage(), details); } finally { try { if (rset != null) { rset.close(); } } catch (Exception details) { if (LOGGER.isWarnEnabled()) { LOGGER.warn(details.getMessage(), details); } } try { if (stmt != null) { stmt.close(); } } catch (Exception details) { if (LOGGER.isWarnEnabled()) { LOGGER.warn(details.getMessage(), details); } } try { if (conn != null) { conn.close(); } } catch (Exception details) { if (LOGGER.isWarnEnabled()) { LOGGER.warn(details.getMessage(), details); } } } } return image; } private ImageRecord getRemoteImage(String aReferent) { ImageRecord image = null; try { URI uri = new URI(aReferent); // Check to see if it's already in the processing queue if (myMigrator.getProcessingList().contains(uri.toString())) { Thread.sleep(1000); int index = 0; while (myMigrator.getProcessingList().contains(uri) && index < (5 * 60)) { Thread.sleep(1000); index++; } if (myRemoteImages.containsKey(aReferent)) { return myRemoteImages.get(aReferent); } } File file = myMigrator.convert(uri); image = new ImageRecord(aReferent, file.getAbsolutePath()); if (file.length() > 0) { myRemoteImages.put(aReferent, image); } else throw new ResolverException( "An error occurred processing file:" + uri.toURL().toString()); } catch (Exception details) { LOGGER.error(StringUtils.formatMessage("Unable to access {} ({})", new String[] { aReferent, details.getMessage() }), details); return null; } return image; } }