diff --git a/src/org/lockss/app/LockssDaemon.java b/src/org/lockss/app/LockssDaemon.java index ed3ae7162f3..0914c6fad26 100644 --- a/src/org/lockss/app/LockssDaemon.java +++ b/src/org/lockss/app/LockssDaemon.java @@ -58,7 +58,7 @@ of this software and associated documentation files (the "Software"), to deal import org.lockss.crawler.*; import org.lockss.remote.*; import org.lockss.clockss.*; -import org.lockss.safenet.*; +import org.lockss.entitlement.*; import org.apache.commons.collections.map.LinkedMap; /** @@ -103,6 +103,9 @@ public class LockssDaemon extends LockssApp { static final long DEFAULT_DAEMON_DEADLINE_REASONABLE_FUTURE = 20 * Constants.WEEK; + public static final String PARAM_KEEPSAFE_ENABLED = Configuration.PREFIX + "entitlement.keepsafe.enabled"; + static final boolean DEFAULT_KEEPSAFE_ENABLED = false; + /** List of local IP addresses to which to bind listen sockets for * servers (admin ui, content, proxy). If not set, servers listen on all * interfaces. Does not affect the port on which various servers listen. @@ -147,7 +150,8 @@ public class LockssDaemon extends LockssApp { public static final String ICP_MANAGER = "IcpManager"; public static final String CRON = "Cron"; public static final String CLOCKSS_PARAMS = "ClockssParams"; - public static final String SAFENET_MANAGER = "SafenetManager"; + public static final String ENTITLEMENT_REGISTRY_CLIENT = "EntitlementRegistryClient"; + public static final String CACHED_ENTITLEMENT_REGISTRY_CLIENT = "CachedEntitlementRegistryClient"; public static final String TRUEZIP_MANAGER = "TrueZipManager"; public static final String DB_MANAGER = "DbManager"; public static final String COUNTER_REPORTS_MANAGER = "CounterReportsManager"; @@ -237,9 +241,13 @@ public class LockssDaemon extends LockssApp { public boolean shouldStart() { return isClockss(); }}, - new ManagerDesc(SAFENET_MANAGER, "org.lockss.safenet.CachingEntitlementRegistryClient") { + new ManagerDesc(ENTITLEMENT_REGISTRY_CLIENT, "org.lockss.entitlement.KeepsafeEntitlementRegistryClient") { + public boolean shouldStart() { + return isKeepsafe(); + }}, + new ManagerDesc(CACHED_ENTITLEMENT_REGISTRY_CLIENT, "org.lockss.entitlement.CachingEntitlementRegistryClient") { public boolean shouldStart() { - return isSafenet(); + return isKeepsafe(); }}, // watchdog last new ManagerDesc(WATCHDOG_SERVICE, DEFAULT_WATCHDOG_SERVICE) @@ -271,7 +279,7 @@ public boolean shouldStart() { private static LockssDaemon theDaemon; private boolean isClockss; - private boolean isSafenet; + private boolean isKeepsafe; protected String testingMode; protected LockssDaemon(List propUrls) { @@ -338,10 +346,10 @@ public boolean isDetectClockssSubscription() { } /** - * True if running as a Safenet daemon + * True if running as a Keepsafe daemon */ - public boolean isSafenet() { - return isSafenet; + public boolean isKeepsafe() { + return isKeepsafe; } /** Stop the daemon. Currently only used in testing. */ @@ -630,7 +638,16 @@ public ClockssParams getClockssParams() { * @throws IllegalArgumentException if the manager is not available. */ public EntitlementRegistryClient getEntitlementRegistryClient() { - return (EntitlementRegistryClient) getManager(SAFENET_MANAGER); + return (EntitlementRegistryClient) getManager(ENTITLEMENT_REGISTRY_CLIENT); + } + + /** + * return the EntitlementRegistryClient instance. + * @return EntitlementRegistryClient instance. + * @throws IllegalArgumentException if the manager is not available. + */ + public EntitlementRegistryClient getCachedEntitlementRegistryClient() { + return (EntitlementRegistryClient) getManager(CACHED_ENTITLEMENT_REGISTRY_CLIENT); } // LockssAuManager accessors @@ -977,7 +994,7 @@ protected void setConfig(Configuration config, Configuration prevConfig, testingMode = config.get(PARAM_TESTING_MODE); String proj = ConfigManager.getPlatformProject(); isClockss = "clockss".equalsIgnoreCase(proj); - isSafenet = "safenet".equalsIgnoreCase(proj); + isKeepsafe = config.getBoolean(PARAM_KEEPSAFE_ENABLED, DEFAULT_KEEPSAFE_ENABLED); super.setConfig(config, prevConfig, changedKeys); } diff --git a/src/org/lockss/safenet/CachingEntitlementRegistryClient.java b/src/org/lockss/entitlement/CachingEntitlementRegistryClient.java similarity index 52% rename from src/org/lockss/safenet/CachingEntitlementRegistryClient.java rename to src/org/lockss/entitlement/CachingEntitlementRegistryClient.java index d63330d6c66..529b19750ed 100644 --- a/src/org/lockss/safenet/CachingEntitlementRegistryClient.java +++ b/src/org/lockss/entitlement/CachingEntitlementRegistryClient.java @@ -1,4 +1,4 @@ -package org.lockss.safenet; +package org.lockss.entitlement; import java.io.IOException; import java.util.Collections; @@ -7,32 +7,35 @@ import org.apache.commons.collections.map.LRUMap; import org.apache.commons.collections.map.MultiKeyMap; -import org.lockss.app.BaseLockssManager; +import org.lockss.app.BaseLockssDaemonManager; import org.lockss.app.ConfigurableManager; import org.lockss.config.Configuration; /* * A very basic cache which just stores the results of the last 100 calls to the Entitlement Registry. - * There's a strong chance this will need to become something more complicated down the line. + * There's a strong chance this will need to become something more complicated down the line if performance isn't acceptable. + * In this case though, it would probably be best to replace it with something like Guava's caching, rather than building something custom. */ -public class CachingEntitlementRegistryClient extends BaseLockssManager implements EntitlementRegistryClient, ConfigurableManager { - private BaseEntitlementRegistryClient client; +public class CachingEntitlementRegistryClient extends BaseLockssDaemonManager implements EntitlementRegistryClient, ConfigurableManager { + public static final String PREFIX = Configuration.PREFIX + "entitlement.cache."; + public static final String PARAM_CACHE_SIZE = PREFIX + "size"; + static final int DEFAULT_CACHE_SIZE = 100; + private EntitlementRegistryClient client; private MultiKeyMap cache; - public CachingEntitlementRegistryClient() { - this(new BaseEntitlementRegistryClient(), 100); - } - - protected CachingEntitlementRegistryClient(BaseEntitlementRegistryClient client, int size) { - this.client = client; - this.cache = MultiKeyMap.decorate(new LRUMap(size)); + public void startService() { + super.startService(); + this.client = getDaemon().getEntitlementRegistryClient(); } public void setConfig(Configuration config, Configuration oldConfig, Configuration.Differences diffs) { - client.setConfig(config, oldConfig, diffs); + if (diffs.contains(PREFIX)) { + int size = config.getInt(PARAM_CACHE_SIZE, DEFAULT_CACHE_SIZE); + this.cache = MultiKeyMap.decorate(new LRUMap(size)); + } } - public boolean isUserEntitled(String issn, String institution, String start, String end) throws IOException { + public synchronized boolean isUserEntitled(String issn, String institution, String start, String end) throws IOException { Object result = this.cache.get("isUserEntitled", issn, institution, start, end); if(result == null) { result = this.client.isUserEntitled(issn, institution, start, end); @@ -41,16 +44,16 @@ public boolean isUserEntitled(String issn, String institution, String start, Str return (Boolean) result; } - public String getPublisher(String issn, String start, String end) throws IOException { - Object result = this.cache.get("getPublisher", issn, start, end); + public synchronized String getPublisher(String issn, String institution, String start, String end) throws IOException { + Object result = this.cache.get("getPublisher", issn, institution, start, end); if(result == null) { - result = this.client.getPublisher(issn, start, end); - this.cache.put("getPublisher", issn, start, end, result); + result = this.client.getPublisher(issn, institution, start, end); + this.cache.put("getPublisher", issn, institution, start, end, result); } return (String) result; } - public PublisherWorkflow getPublisherWorkflow(String publisherName) throws IOException { + public synchronized PublisherWorkflow getPublisherWorkflow(String publisherName) throws IOException { Object result = this.cache.get("getPublisherWorkflow", publisherName); if(result == null) { result = this.client.getPublisherWorkflow(publisherName); @@ -59,7 +62,7 @@ public PublisherWorkflow getPublisherWorkflow(String publisherName) throws IOExc return (PublisherWorkflow) result; } - public String getInstitution(String scope) throws IOException { + public synchronized String getInstitution(String scope) throws IOException { Object result = this.cache.get("getInstitution", scope); if(result == null) { result = this.client.getInstitution(scope); @@ -67,6 +70,5 @@ public String getInstitution(String scope) throws IOException { } return (String) result; } - } diff --git a/src/org/lockss/safenet/EntitlementRegistryClient.java b/src/org/lockss/entitlement/EntitlementRegistryClient.java similarity index 74% rename from src/org/lockss/safenet/EntitlementRegistryClient.java rename to src/org/lockss/entitlement/EntitlementRegistryClient.java index b32a029c4a7..f5016c60102 100644 --- a/src/org/lockss/safenet/EntitlementRegistryClient.java +++ b/src/org/lockss/entitlement/EntitlementRegistryClient.java @@ -1,4 +1,4 @@ -package org.lockss.safenet; +package org.lockss.entitlement; import java.io.IOException; @@ -7,6 +7,6 @@ public interface EntitlementRegistryClient extends LockssManager { boolean isUserEntitled(String issn, String institution, String start, String end) throws IOException; String getInstitution(String scope) throws IOException; - String getPublisher(String issn, String start, String end) throws IOException; + String getPublisher(String issn, String institution, String start, String end) throws IOException; PublisherWorkflow getPublisherWorkflow(String publisherGuid) throws IOException; } diff --git a/src/org/lockss/safenet/BaseEntitlementRegistryClient.java b/src/org/lockss/entitlement/KeepsafeEntitlementRegistryClient.java similarity index 67% rename from src/org/lockss/safenet/BaseEntitlementRegistryClient.java rename to src/org/lockss/entitlement/KeepsafeEntitlementRegistryClient.java index d2815630ead..0a05daf4797 100644 --- a/src/org/lockss/safenet/BaseEntitlementRegistryClient.java +++ b/src/org/lockss/entitlement/KeepsafeEntitlementRegistryClient.java @@ -1,10 +1,9 @@ -package org.lockss.safenet; +package org.lockss.entitlement; import java.io.IOException; import java.net.URISyntaxException; import java.text.DateFormat; import java.text.ParseException; -import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; @@ -13,6 +12,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.JsonNode; +import org.apache.commons.lang3.time.FastDateFormat; import org.apache.http.NameValuePair; import org.apache.http.client.utils.URIBuilder; import org.apache.http.message.BasicNameValuePair; @@ -20,61 +20,84 @@ import org.lockss.app.BaseLockssManager; import org.lockss.app.ConfigurableManager; import org.lockss.config.Configuration; +import org.lockss.util.Constants; import org.lockss.util.IOUtil; import org.lockss.util.Logger; import org.lockss.util.UrlUtil; import org.lockss.util.urlconn.LockssUrlConnection; +import org.lockss.util.urlconn.LockssUrlConnectionPool; -public class BaseEntitlementRegistryClient extends BaseLockssManager implements EntitlementRegistryClient, ConfigurableManager { +public class KeepsafeEntitlementRegistryClient extends BaseLockssManager implements EntitlementRegistryClient, ConfigurableManager { - private static final Logger log = Logger.getLogger(BaseEntitlementRegistryClient.class); + private static final Logger log = Logger.getLogger(KeepsafeEntitlementRegistryClient.class); - public static final String PREFIX = Configuration.PREFIX + "safenet."; + public static final String PREFIX = Configuration.PREFIX + "entitlement.keepsafe."; public static final String PARAM_ER_URI = PREFIX + "registryUri"; static final String DEFAULT_ER_URI = ""; public static final String PARAM_ER_APIKEY = PREFIX + "apiKey"; static final String DEFAULT_ER_APIKEY = ""; - private static final DateFormat dateFormat = new SimpleDateFormat("yyyyMMdd"); + public static final String PARAM_CONNECT_TIMEOUT = PREFIX + "timeout.connect"; + static final long DEFAULT_CONNECT_TIMEOUT = 30 * Constants.SECOND; + public static final String PARAM_DATA_TIMEOUT = PREFIX + "timeout.data"; + static final long DEFAULT_DATA_TIMEOUT = 120 * Constants.SECOND; + public static final String PARAM_CLOSE_IDLE_CONNECTION_IDLE_TIME = PREFIX + "closeIdleConnections.idleTime"; + static final long DEFAULT_CLOSE_IDLE_CONNECTION_IDLE_TIME = 10 * Constants.MINUTE; + private static final FastDateFormat dateFormat = FastDateFormat.getInstance("yyyyMMdd"); + + private LockssUrlConnectionPool connectionPool; + private long paramCloseIdleConnectionsIdleTime = DEFAULT_CLOSE_IDLE_CONNECTION_IDLE_TIME; private ObjectMapper objectMapper; private String erUri; private String apiKey; - public BaseEntitlementRegistryClient() { + public KeepsafeEntitlementRegistryClient() { this.objectMapper = new ObjectMapper(); + this.connectionPool = new LockssUrlConnectionPool(); } public void setConfig(Configuration config, Configuration oldConfig, Configuration.Differences diffs) { if (diffs.contains(PREFIX)) { erUri = config.get(PARAM_ER_URI, DEFAULT_ER_URI); apiKey = config.get(PARAM_ER_APIKEY, DEFAULT_ER_APIKEY); + long connectTimeout = config.getTimeInterval(PARAM_CONNECT_TIMEOUT, DEFAULT_CONNECT_TIMEOUT); + long dataTimeout = config.getTimeInterval(PARAM_DATA_TIMEOUT, DEFAULT_DATA_TIMEOUT); + paramCloseIdleConnectionsIdleTime = config.getTimeInterval(PARAM_CLOSE_IDLE_CONNECTION_IDLE_TIME, DEFAULT_CLOSE_IDLE_CONNECTION_IDLE_TIME); + connectionPool.setConnectTimeout(connectTimeout); + connectionPool.setDataTimeout(dataTimeout); } } public boolean isUserEntitled(String issn, String institution, String start, String end) throws IOException { + JsonNode entitlement = this.findMatchingEntitlement(issn, institution, start, end); + return entitlement != null; + } + + private JsonNode findMatchingEntitlement(String issn, String institution, String start, String end) throws IOException { Map parameters = new HashMap(); - parameters.put("api_key", apiKey); parameters.put("identifier_value", issn); parameters.put("institution", institution); parameters.put("start", start); parameters.put("end", end); + parameters.put("validate", "1"); JsonNode entitlements = callEntitlementRegistry("/entitlements", parameters); if (entitlements != null) { for(JsonNode entitlement : entitlements) { JsonNode entitlementInstitution = entitlement.get("institution"); - if (entitlementInstitution != null && entitlementInstitution.asText().equals(institution)) { + log.debug("Checking entitlement " + entitlement.toString()); + if (entitlementInstitution != null && entitlementInstitution.asText().endsWith(institution + "/")) { log.warning("TODO: Verify title and dates"); - return true; + return entitlement; } } // Valid request, but the entitlements don't match the information we passed, which should never happen - throw new IOException("No matching entitlements returned from entitlement registry"); + log.error("Entitlements returned from entitlement registry do not match passed parameters"); } //Valid request, no entitlements found - return false; + return null; } private Date extractDate(String value) throws IOException { @@ -96,46 +119,22 @@ private Date extractDate(JsonNode node, String key) throws IOException { return extractDate(value.asText()); } - public String getPublisher(String issn, String start, String end) throws IOException { - Map parameters = new HashMap(); - parameters.put("identifier", issn); - Date startDate = extractDate(start); - Date endDate = extractDate(end); - JsonNode titles = callEntitlementRegistry("/titles", parameters); - if (titles != null) { - List foundPublishers = new ArrayList(); - for(JsonNode title : titles) { - JsonNode publishers = title.get("publishers"); - for(JsonNode publisher : publishers) { - Date foundStartDate = extractDate(publisher, "start"); - Date foundEndDate = extractDate(publisher, "end"); - - if ( foundStartDate != null && ( startDate == null || foundStartDate.after(startDate) ) ) { - continue; - } - if ( foundEndDate != null && ( endDate == null || foundEndDate.before(endDate) ) ) { - continue; - } - foundPublishers.add(publisher.get("id").asText()); - } - } - if (foundPublishers.size() > 1) { - // Valid request, but there are multiple publishers for the date range, which should never happen - throw new IOException("Multiple matching publishers returned from entitlement registry"); - } - if (foundPublishers.size() == 1) { - return foundPublishers.get(0); - } + public String getPublisher(String issn, String institution, String start, String end) throws IOException { + JsonNode entitlement = this.findMatchingEntitlement(issn, institution, start, end); + if ( entitlement == null ) { + return null; } - // Valid request, no publisher found - return null; + + String url = entitlement.get("publisher").asText(); + String[] parts = url.split("/"); + return parts[parts.length - 1]; } public PublisherWorkflow getPublisherWorkflow(String publisherGuid) throws IOException { Map parameters = new HashMap(); JsonNode publisher = callEntitlementRegistry("/publishers/"+publisherGuid, parameters); if (publisher != null) { - JsonNode foundGuid = publisher.get("id"); + JsonNode foundGuid = publisher.get("guid"); if (foundGuid != null && foundGuid.asText().equals(publisherGuid)) { JsonNode foundWorkflow = publisher.get("workflow"); if(foundWorkflow != null) { @@ -147,6 +146,10 @@ public PublisherWorkflow getPublisherWorkflow(String publisherGuid) throws IOExc throw new IOException("No valid workflow returned from entitlement registry: " + foundWorkflow.asText().toUpperCase()); } } + else { + log.warning("No workflow set for publisher, defaulting to PRIMARY_LOCKSS"); + return PublisherWorkflow.PRIMARY_LOCKSS; + } } } // Valid request, but no valid workflow information was returned, which should never happen @@ -169,7 +172,7 @@ public String getInstitution(String scope) throws IOException { if (!scope.equals(institution.get("scope").asText())) { throw new IOException("No matching institutions returned from entitlement registry"); } - return institution.get("id").asText(); + return institution.get("guid").asText(); } throw new IOException("No matching institutions returned from entitlement registry"); } @@ -190,6 +193,8 @@ private JsonNode callEntitlementRegistry(String endpoint, List pa String url = builder.toString(); log.debug("Connecting to ER at " + url); connection = openConnection(url); + connection.setRequestProperty("Accept", "application/json"); + connection.setRequestProperty("Authorization", "Token " + apiKey); connection.execute(); int responseCode = connection.getResponseCode(); if (responseCode == 200) { @@ -210,12 +215,13 @@ else if (responseCode == 204) { if(connection != null) { IOUtil.safeRelease(connection); } + connectionPool.closeIdleConnections(paramCloseIdleConnectionsIdleTime); } } // protected so that it can be overriden with mock connections in tests protected LockssUrlConnection openConnection(String url) throws IOException { - return UrlUtil.openConnection(url); + return UrlUtil.openConnection(url, connectionPool); } protected static List mapToPairs(Map params) { diff --git a/src/org/lockss/entitlement/PublisherWorkflow.java b/src/org/lockss/entitlement/PublisherWorkflow.java new file mode 100644 index 00000000000..7b48236c55b --- /dev/null +++ b/src/org/lockss/entitlement/PublisherWorkflow.java @@ -0,0 +1,15 @@ +package org.lockss.entitlement; + +/** + * When running a network where content is not available to all users, agreements with the publishers may limit how that content should be used + * This enum encapsulates the different options, allowing the EntitlementRegistry to determine, based on the publisher and other information where any content should be served from + */ +public enum PublisherWorkflow { + /* Serve the content from LOCKSS first, only attempting to pass the request to the publisher if it cannot be found */ + PRIMARY_LOCKSS, + /* Pass the request to the publisher first, only attempting to use the archived copy if the publisher does not serve it */ + PRIMARY_PUBLISHER, + /* Pass the request to the publisher first, if the publisher does not serve it then display some error */ + LIBRARY_NOTIFICATION +}; + diff --git a/src/org/lockss/metadata/MetadataManager.java b/src/org/lockss/metadata/MetadataManager.java index f0ffd1c5612..6a54c325c19 100644 --- a/src/org/lockss/metadata/MetadataManager.java +++ b/src/org/lockss/metadata/MetadataManager.java @@ -50,12 +50,14 @@ of this software and associated documentation files (the "Software"), to deal import org.lockss.db.DbException; import org.lockss.db.DbManager; import org.lockss.db.PkNamePair; +import org.lockss.exporter.biblio.BibliographicItem; import org.lockss.extractor.ArticleMetadataExtractor; import org.lockss.extractor.BaseArticleMetadataExtractor; import org.lockss.extractor.MetadataField; import org.lockss.extractor.MetadataTarget; import org.lockss.plugin.ArchivalUnit; import org.lockss.plugin.AuUtil; +import org.lockss.plugin.CachedUrl; import org.lockss.plugin.Plugin; import org.lockss.plugin.Plugin.Feature; import org.lockss.plugin.PluginManager; @@ -4533,4 +4535,38 @@ public Collection> getDbArchivalUnitsDeletedFromDaemon() public boolean deleteDbAu(Long auSeq, String auKey) throws DbException { return getMetadataManagerSql().removeAu(auSeq, auKey); } + + /** + * Finds the bibliographic information held about a CachedUrl. + * + * @param cu + * A CachedUrl to lookup. + * @return a BibliographicItem with appropriate infomation populated if the CachedUrl was found in the database, + * null otherwise. + * @throws DbException + * if any problem occurred accessing the database. + */ + public BibliographicItem getCUBibliographicInfo(CachedUrl cu) throws DbException { + // Get a connection to the database. + Connection conn = dbManager.getConnection(); + + return getCUBibliographicInfo(conn, cu); + } + + /** + * Finds the bibliographic information held about a CachedUrl. + * + * @param conn + * A Connection with the database connection to be used. + * @param cu + * A CachedUrl to lookup. + * @return a BibliographicItem with appropriate infomation populated if the CachedUrl was found in the database, + * null otherwise. + * @throws DbException + * if any problem occurred accessing the database. + */ + public BibliographicItem getCUBibliographicInfo(Connection conn, CachedUrl cu) throws DbException { + return mdManagerSql.getCUBibliographicInfo(conn, cu); + } + } diff --git a/src/org/lockss/metadata/MetadataManagerSql.java b/src/org/lockss/metadata/MetadataManagerSql.java index 57a519ad0c4..801d693de65 100644 --- a/src/org/lockss/metadata/MetadataManagerSql.java +++ b/src/org/lockss/metadata/MetadataManagerSql.java @@ -46,9 +46,12 @@ of this software and associated documentation files (the "Software"), to deal import org.lockss.db.DbException; import org.lockss.db.DbManager; import org.lockss.db.PkNamePair; +import org.lockss.exporter.biblio.BibliographicItem; +import org.lockss.exporter.biblio.BibliographicItemImpl; import org.lockss.metadata.MetadataManager.PrioritizedAuId; import org.lockss.plugin.ArchivalUnit; import org.lockss.plugin.AuUtil; +import org.lockss.plugin.CachedUrl; import org.lockss.plugin.PluginManager; import org.lockss.util.KeyPair; import org.lockss.util.Logger; @@ -1575,6 +1578,21 @@ public class MetadataManagerSql { + AU_SEQ_COLUMN + " = ?" + " and " + AU_KEY_COLUMN + " = ?"; + // Query to fetch the date and ISSN of a CachedUrl + private static final String GET_CU_BIB_INFO_QUERY = "select distinct " + + "mi." + DATE_COLUMN + + ", i." + ISSN_COLUMN + + ", i." + ISSN_TYPE_COLUMN + + " from " + + URL_TABLE + " u" + + ", " + MD_ITEM_TABLE + " mi" + + ", " + ISSN_TABLE + " i" + + " where " + + "u." + URL_COLUMN + "= ?" + + " and u." + FEATURE_COLUMN + "='" + MetadataManager.ACCESS_URL_FEATURE + "'" + + " and u." + MD_ITEM_SEQ_COLUMN + "=" + "mi." + MD_ITEM_SEQ_COLUMN + + " and i." + MD_ITEM_SEQ_COLUMN + "=" + "mi." + PARENT_SEQ_COLUMN; + private DbManager dbManager; private MetadataManager metadataManager; @@ -7215,4 +7233,67 @@ private boolean removeAu(Connection conn, Long auSeq, String auKey) log.debug2(DEBUG_HEADER + "result = " + (deletedCount > 0)); return deletedCount > 0; } + + /** + * Finds the bibliographic information held about a CachedUrl. + * + * @param conn + * A Connection with the database connection to be used. + * @param cu + * A CachedUrl to lookup. + * @return a BibliographicItem with appropriate infomation populated if the CachedUrl was found in the database, + * null otherwise. + * @throws DbException + * if any problem occurred accessing the database. + */ + public BibliographicItem getCUBibliographicInfo(Connection conn, CachedUrl cu) throws DbException { + final String DEBUG_HEADER = "getCUBibliographicInfo(): "; + if (log.isDebug2()) log.debug2(DEBUG_HEADER + "cu = " + cu); + String query = GET_CU_BIB_INFO_QUERY; + PreparedStatement stmt = null; + ResultSet resultSet = null; + try { + // return all related values for debugging purposes + + stmt = dbManager.prepareStatement(conn, query); + + ArrayList args = new ArrayList(); + args.add(cu.getUrl()); + for (int i = 0; i < args.size(); i++) { + if (log.isDebug3()) log.debug3(DEBUG_HEADER + "arg " + i + " = " + args.get(i)); + stmt.setString(i + 1, args.get(i)); + } + + resultSet = dbManager.executeQuery(stmt); + + boolean foundResults = false; + BibliographicItemImpl item = new BibliographicItemImpl(); + while ( resultSet.next() ) { + foundResults = true; + String year = resultSet.getString(1); + String issn = resultSet.getString(2); + String issnType = resultSet.getString(3); + item.setYear(year); + if(P_ISSN_TYPE.equals(issnType)){ + item.setPrintIssn(issn); + } + else if(E_ISSN_TYPE.equals(issnType)){ + item.setEissn(issn); + } + else if(L_ISSN_TYPE.equals(issnType)){ + item.setIssnL(issn); + } + } + if (log.isDebug2()) log.debug2(DEBUG_HEADER + "result = " + item); + return foundResults ? item : null; + } catch (SQLException sqle) { + String message = "Cannot get the BibliogaphicItem"; + log.error(message, sqle); + log.error("SQL = '" + query + "'."); + throw new DbException(message, sqle); + } finally { + DbManager.safeCloseResultSet(resultSet); + DbManager.safeCloseStatement(stmt); + } + } } diff --git a/src/org/lockss/safenet/PublisherWorkflow.java b/src/org/lockss/safenet/PublisherWorkflow.java deleted file mode 100644 index 8fa6186019f..00000000000 --- a/src/org/lockss/safenet/PublisherWorkflow.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.lockss.safenet; - -public enum PublisherWorkflow { - PRIMARY_SAFENET, PRIMARY_PUBLISHER, LIBRARY_NOTIFICATION -}; - diff --git a/src/org/lockss/servlet/ContentServletManager.java b/src/org/lockss/servlet/ContentServletManager.java index c44d0e9629a..41bb69478df 100644 --- a/src/org/lockss/servlet/ContentServletManager.java +++ b/src/org/lockss/servlet/ContentServletManager.java @@ -139,6 +139,13 @@ protected ManagerInfo getManagerInfo() { "Serve Content", ServletDescr.NO_NAV_TABLE); + public static final ServletDescr SERVLET_ENTITLEMENT_CHECK_SERVE_CONTENT = + new ServletDescr("ServeContent", + EntitlementCheckServeContent.class, + "Serve Content", + "ServeContent", + ServletDescr.NO_NAV_TABLE); + public static final ServletDescr SERVLET_SERVE_CONTENT = new ServletDescr("ServeContent", ServeContent.class, @@ -192,6 +199,10 @@ static String mailtoUrl(String addr) { SERVLET_SERVE_CONTENT_NO_NAV, }; + static final ServletDescr servletDescrsKeepsafe[] = { + SERVLET_ENTITLEMENT_CHECK_SERVE_CONTENT, + }; + // All servlets must be listed here (even if not in nav table). // Order of descrs determines order in nav table. static final ServletDescr servletDescrs[] = { @@ -203,7 +214,9 @@ static String mailtoUrl(String addr) { }; public ServletDescr[] getServletDescrs() { - if (CurrentConfig.getBooleanParam(PARAM_CONTENT_ONLY, + if (LockssDaemon.getLockssDaemon().isKeepsafe()) { + return servletDescrsKeepsafe; + } else if (CurrentConfig.getBooleanParam(PARAM_CONTENT_ONLY, DEFAULT_CONTENT_ONLY)) { return servletDescrsNoNav; } else { diff --git a/src/org/lockss/servlet/EntitlementCheckServeContent.java b/src/org/lockss/servlet/EntitlementCheckServeContent.java new file mode 100644 index 00000000000..3c3e19c29f7 --- /dev/null +++ b/src/org/lockss/servlet/EntitlementCheckServeContent.java @@ -0,0 +1,386 @@ +/* + * $Id$ + */ + +/* + +Copyright (c) 2000-2016 Board of Trustees of Leland Stanford Jr. University, +all rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +STANFORD UNIVERSITY BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Stanford University shall not +be used in advertising or otherwise to promote the sale, use or other dealings +in this Software without prior written authorization from Stanford University. + +*/ + +package org.lockss.servlet; + +import java.io.*; +import java.net.*; +import java.sql.*; +import java.util.*; +import java.util.List; +import java.util.regex.*; + +import javax.servlet.*; + +import static org.lockss.db.SqlConstants.*; + +import org.apache.commons.collections.*; +import org.lockss.app.LockssDaemon; +import org.lockss.config.*; +import org.lockss.daemon.*; +import org.lockss.daemon.OpenUrlResolver.OpenUrlInfo; +import org.lockss.db.*; +import org.lockss.exporter.biblio.*; +import org.lockss.exporter.counter.*; +import org.lockss.extractor.*; +import org.lockss.metadata.MetadataManager; +import org.lockss.plugin.*; +import org.lockss.plugin.PluginManager.CuContentReq; +import org.lockss.entitlement.EntitlementRegistryClient; +import org.lockss.entitlement.PublisherWorkflow; +import org.lockss.util.*; +import org.lockss.util.urlconn.*; +import org.mortbay.html.*; +import org.mortbay.http.*; + +@SuppressWarnings("serial") +public class EntitlementCheckServeContent extends ServeContent { + + private static final Logger log = Logger.getLogger(EntitlementCheckServeContent.class); + + // If true, scope can be 'mocked' from the URL parameters. This is for testing purposes, and should never be true in production + public static final String PARAM_MOCK_SCOPE = PREFIX + "mockScope"; + + private static final String INSTITUTION_HEADER = "X-Lockss-Institution"; + + public static final String INSTITUTION_SCOPE_SESSION_KEY = "scope"; + + private static boolean mockScope = false; + + private PublisherWorkflow workflow; + private String institution; + private String issn; + private String start; + private String end; + private EntitlementRegistryClient entitlementRegistry; + private MetadataManager metadataManager; + + // don't hold onto objects after request finished + protected void resetLocals() { + workflow = null; + institution = null; + issn = null; + start = null; + end = null; + super.resetLocals(); + } + + public void init(ServletConfig config) throws ServletException { + super.init(config); + LockssDaemon daemon = getLockssDaemon(); + entitlementRegistry = daemon.getCachedEntitlementRegistryClient(); + metadataManager = daemon.getMetadataManager(); + } + + /** Called by ServletUtil.setConfig() */ + static void setConfig(Configuration config, + Configuration oldConfig, + Configuration.Differences diffs) { + ServeContent.setConfig(config, oldConfig, diffs); + if (diffs.contains(PREFIX)) { + mockScope = config.getBoolean(PARAM_MOCK_SCOPE, false); + } + } + + protected boolean isNeverProxyForAu(ArchivalUnit archivalUnit) { + return super.isNeverProxyForAu(archivalUnit) || workflow == PublisherWorkflow.PRIMARY_LOCKSS; + } + + /** + * Handle a request + * @throws IOException + */ + public void lockssHandleRequest() throws IOException { + + if ( mockScope ) { + String userInstScope = req.getParameter(INSTITUTION_SCOPE_SESSION_KEY); + if ( ! "".equals(userInstScope) ) { + log.warning("Setting scope from parameters:"+userInstScope+" This should not be done in production"); + this.getSession().setAttribute(INSTITUTION_SCOPE_SESSION_KEY, userInstScope); + } + } + + updateInstitution(); + + super.lockssHandleRequest(); + } + + protected boolean setCachedUrlAndAu() throws IOException { + // Find a CU that the user is entitled to access, and with content + List cachedUrls = pluginMgr.findCachedUrls(url, CuContentReq.HasContent); + if(cachedUrls != null && !cachedUrls.isEmpty()) { + for(CachedUrl cachedUrl: cachedUrls) { + try { + if(isUserEntitled(cachedUrl, cachedUrl.getArchivalUnit())) { + cu = cachedUrl; + au = cu.getArchivalUnit(); + if (log.isDebug3()) log.debug("cu: " + cu + " au: " + au); + break; + } + } + catch (IOException e) { + // We can't communicate with the ER, so we have to assume that we can't give the user access to the content at the moment + log.error("Error communicating with entitlement registry: " + e); + handleEntitlementRegistryErrorUrlRequest(url); + return false; + } + catch (IllegalArgumentException e) { + // We don't have enough information about the AU to determine if the user is entitled, but there's nothing they can do about it + log.error("Error with AU configuration: " + e); + handleMissingUrlRequest(url, PubState.KnownDown); + return false; + } + } + if(cu == null) { + // We found at least one CachedUrl, which means the content is preserved, but the user wasn't entitled to any of them + handleUnauthorisedUrlRequest(url); + return false; + } + } + return true; + } + + + /** + * Handle request for content that belongs to one of our AUs, whether or not + * we have content for that URL. If this request contains a version param, + * serve it from cache with a Memento-Datetime header and no + * link-rewriting. For requests without a version param, rewrite links, + * and serve from publisher if publisher provides it and the daemon options + * allow it; otherwise, try to serve from cache. + * + * @throws IOException for IO errors + */ + protected void handleAuRequest() throws IOException { + try { + if (!isUserEntitled(cu, au)) { + handleUnauthorisedUrlRequest(url); + return; + } + workflow = getPublisherWorkflow(cu, au); + if (workflow == PublisherWorkflow.LIBRARY_NOTIFICATION) { + handleUnauthorisedUrlRequest(url); + return; + } + } + catch (IOException e) { + // We can't communicate with the ER, so we have to assume that we can't give the user access to the content at the moment + log.error("Error communicating with entitlement registry: " + e); + handleEntitlementRegistryErrorUrlRequest(url); + return; + } + catch (IllegalArgumentException e) { + // We don't have enough information about the AU to determine if the user is entitled, but there's nothing they can do about it + log.error("Error with AU configuration: " + e); + handleMissingUrlRequest(url, PubState.KnownDown); + return; + } + + super.handleAuRequest(); + } + + @Override + protected LockssUrlConnection openConnection(String url, LockssUrlConnectionPool pool) throws IOException { + LockssUrlConnection conn = doOpenConnection(url, pool); + conn.addRequestProperty(INSTITUTION_HEADER, (String) getSession().getAttribute(INSTITUTION_SCOPE_SESSION_KEY)); + return conn; + } + + // Extracted out so that the connection can be mocked in test classes + protected LockssUrlConnection doOpenConnection(String url, LockssUrlConnectionPool pool) throws IOException { + return super.openConnection(url, pool); + } + + protected void handleEntitlementRegistryErrorUrlRequest(String missingUrl) + throws IOException { + handleUrlRequestError(missingUrl, PubState.KnownDown, "An error occurred trying to access the requested URL on this LOCKSS box. This may be temporary and you may wish to report this, and try again later. ", HttpResponse.__503_Service_Unavailable, "entitlement registry error"); + } + + protected void handleUnauthorisedUrlRequest(String missingUrl) + throws IOException { + handleUrlRequestError(missingUrl, PubState.KnownDown, "You are not authorised to access the requested URL on this LOCKSS box. ", HttpResponse.__403_Forbidden, "unauthorised"); + } + + + void updateInstitution() throws IOException { + String institutionScope = (String) this.getSession().getAttribute(INSTITUTION_SCOPE_SESSION_KEY); + institution = entitlementRegistry.getInstitution(institutionScope); + } + + boolean isUserEntitled(CachedUrl cachedUrl, ArchivalUnit archivalUnit) throws IOException, IllegalArgumentException { + validateBibInfo(cachedUrl, archivalUnit); + + return entitlementRegistry.isUserEntitled(issn, institution, start, end); + } + + PublisherWorkflow getPublisherWorkflow(CachedUrl cachedUrl, ArchivalUnit archivalUnit) throws IOException, IllegalArgumentException { + validateBibInfo(cachedUrl, archivalUnit); + + String publisher = entitlementRegistry.getPublisher(issn, institution, start, end); + if(StringUtil.isNullString(publisher)) { + throw new IllegalArgumentException("No publisher found"); + } + + return entitlementRegistry.getPublisherWorkflow(publisher); + } + + private void setBibInfoFromBibliographicItem(BibliographicItem item) { + log.debug2("Setting bib info from BibliographicItem"); + if(!StringUtil.isNullString(issn) && !StringUtil.isNullString(start) && !StringUtil.isNullString(end)) { + log.debug2("Bib info already set"); + return; + } + + if(item == null) { + log.debug2("No BibliographicItem"); + return; + } + + if(StringUtil.isNullString(issn)) { + issn = item.getIssn(); + log.debug("Setting issn to " + issn); + } + + if(StringUtil.isNullString(start)) { + // Despite being called StartYear, this is actually a full date + start = item.getStartYear(); + log.debug("Setting start to " + start); + } + + if(StringUtil.isNullString(end)) { + end = item.getEndYear(); + log.debug("Setting end to " + end); + } + } + + private void setBibInfoFromMetadata(ArticleMetadata md) { + log.debug2("Setting bib info from TDB"); + if(!StringUtil.isNullString(issn) && !StringUtil.isNullString(start) && !StringUtil.isNullString(end)) { + log.debug2("Bib info already set"); + return; + } + + if(md == null) { + log.debug2("No ArticleMetadata"); + return; + } + + BibliographicItemImpl item = new BibliographicItemImpl(); + item.setPrintIssn(md.get("issn")); + item.setEissn(md.get("eissn")); + item.setIssnL(md.get("issnl")); + item.setYear(md.get("date")); + setBibInfoFromBibliographicItem(item); + } + + private void setBibInfoFromTdb(ArchivalUnit archivalUnit) throws IllegalArgumentException { + log.debug2("Setting bib info from TDB"); + if(!StringUtil.isNullString(issn) && !StringUtil.isNullString(start) && !StringUtil.isNullString(end)) { + log.debug2("Bib info already set"); + return; + } + + if(archivalUnit == null) { + log.debug2("No ArchivalUnit"); + return; + } + + TdbAu tdbAu = archivalUnit.getTdbAu(); + if(tdbAu == null) { + log.debug2("No TdbAu"); + return; + } + + if(StringUtil.isNullString(issn)) { + issn = tdbAu.getIssn(); + log.debug("Setting issn to " + issn); + } + + if(StringUtil.isNullString(start)) { + start = tdbAu.getStartYear(); + if(!StringUtil.isNullString(start)) { + start += "0101"; + } + log.debug("Setting start to " + start); + } + + if(StringUtil.isNullString(end)) { + end = tdbAu.getEndYear(); + if(!StringUtil.isNullString(end)) { + end += "1231"; + } + log.debug("Setting end to " + end); + } + } + + private void setBibInfoFromMetadataDB(CachedUrl cachedUrl) { + log.debug2("Setting bib info from database"); + if(!StringUtil.isNullString(issn) && !StringUtil.isNullString(start) && !StringUtil.isNullString(end)) { + log.debug2("Bib info already set"); + return; + } + + if(cachedUrl == null) { + log.debug2("No CachedUrl"); + return; + } + + try { + BibliographicItem item = metadataManager.getCUBibliographicInfo(cachedUrl); + if ( item != null ) { + setBibInfoFromBibliographicItem(item); + } + } catch (DbException e) { + log.error("Error fetching metadata", e); + } + } + + private void validateBibInfo(CachedUrl cachedUrl, ArchivalUnit archivalUnit) { + setBibInfoFromMetadataDB(cachedUrl); + setBibInfoFromTdb(archivalUnit); + + if(StringUtil.isNullString(issn)) { + throw new IllegalArgumentException("ArchivalUnit has no ISSN"); + } + if(StringUtil.isNullString(start)) { + throw new IllegalArgumentException("ArchivalUnit has no start year"); + } + if(StringUtil.isNullString(end)) { + throw new IllegalArgumentException("ArchivalUnit has no end year"); + } + } + + void logAccess(String url, String msg) { + super.logAccess(url, "UA: \"" + req.getHeader("User-Agent") + "\" " + msg); + } +} + diff --git a/src/org/lockss/servlet/ServeContent.java b/src/org/lockss/servlet/ServeContent.java index 81efb9fd9c0..60bc81f83f4 100644 --- a/src/org/lockss/servlet/ServeContent.java +++ b/src/org/lockss/servlet/ServeContent.java @@ -303,28 +303,28 @@ public static enum RewriteStyle { private static boolean paramAccessAlertsEnabled = DEFAULT_ACCESS_ALERTS_ENABLED; private static boolean processForms = DEFAULT_PROCESS_FORMS; - private static String candidates404Msg = DEFAULT_404_CANDIDATES_MSG; + protected static String candidates404Msg = DEFAULT_404_CANDIDATES_MSG; private static int loginCheckerBufSize = BaseUrlFetcher.DEFAULT_LOGIN_CHECKER_MARK_LIMIT; - private ArchivalUnit au; + protected ArchivalUnit au; private ArchivalUnit explicitAu; - private String url; + protected String url; private String cuUrl; // CU's url (might differ from incoming url due to // normalizaton) private String baseUrl; // The base URL to use for resolving relative // links when rewriting. If redirected, this is // the URL from which the content was served. - private String versionStr; // non-null iff handling a (possibly-invalid) + protected String versionStr; // non-null iff handling a (possibly-invalid) // Memento request - private CachedUrl cu; - private boolean enabledPluginsOnly; + protected CachedUrl cu; + protected boolean enabledPluginsOnly; private String accessLogInfo; private AccessLogType requestType = AccessLogType.None; - private PluginManager pluginMgr; - private ProxyManager proxyMgr; + protected PluginManager pluginMgr; + protected ProxyManager proxyMgr; private OpenUrlResolver openUrlResolver; // don't hold onto objects after request finished @@ -421,6 +421,9 @@ protected boolean isInCache() { if (res && explicitAu != null) { pluginMgr.promoteAuInSearchSets(explicitAu); } + if(log.isDebug2()) { + log.debug2("isInCache: " + res + ", cu: " + (cu != null) + ", hasContent: " + (cu != null && cu.hasContent()) + ", explicitAu: " + (explicitAu != null)); + } return res; } @@ -751,14 +754,11 @@ protected void handleUrlRequest() throws IOException { } } else if (!isMementoRequest()) { - // Find a CU with content if possible. If none, find an AU where - // it would fit so can rewrite content from publisher if necessary. - cu = pluginMgr.findCachedUrl(url, CuContentReq.PreferContent); - if (cu != null) { - cuUrl = cu.getUrl(); - au = cu.getArchivalUnit(); - if (log.isDebug3()) log.debug3("cu: " + cu + " au: " + au); - } + boolean findCachedUrl = setCachedUrlAndAu(); + // Returns false if there is an error that has already been handled + if (!findCachedUrl) { + return; + } } else { /* * This is a Memento request, and the AU param was provided, but we @@ -790,6 +790,18 @@ protected void handleUrlRequest() throws IOException { } } + protected boolean setCachedUrlAndAu() throws IOException { + // Find a CU with content if possible. If none, find an AU where + // it would fit so can rewrite content from publisher if necessary. + cu = pluginMgr.findCachedUrl(url, CuContentReq.PreferContent); + if (cu != null) { + cuUrl = cu.getUrl(); + au = cu.getArchivalUnit(); + if (log.isDebug3()) log.debug3("cu: " + cu + " au: " + au); + } + return true; + } + /** * Given a CachedUrl and a string representation of a version number, returns * that version of the CachedUrl. Has no side effects within this instance. @@ -801,7 +813,7 @@ protected void handleUrlRequest() throws IOException { * @throws VersionNotFoundException if cachedUrl lacks the requested version * @throws RuntimeException */ - private static CachedUrl getHistoricalCu(CachedUrl cachedUrl, String verStr) + protected static CachedUrl getHistoricalCu(CachedUrl cachedUrl, String verStr) throws NumberFormatException, VersionNotFoundException, RuntimeException { CachedUrl result; int version = Integer.parseInt(verStr); @@ -818,7 +830,7 @@ private static CachedUrl getHistoricalCu(CachedUrl cachedUrl, String verStr) return result; } - private static class VersionNotFoundException extends Exception {} + protected static class VersionNotFoundException extends Exception {} /** * Redirect to the current URL. Uses response redirection @@ -1096,7 +1108,7 @@ protected void handleAuRequest() throws IOException { /** * @return true iff the user is requesting a particular version of the content */ - private boolean isMementoRequest() { + protected boolean isMementoRequest() { return !StringUtil.isNullString(versionStr); } @@ -1417,7 +1429,7 @@ protected LockssUrlConnection openLockssUrlConnection(LockssUrlConnectionPool } else { fwdUrl = url; } - LockssUrlConnection conn = UrlUtil.openConnection(fwdUrl, pool); + LockssUrlConnection conn = openConnection(fwdUrl, pool); // check connection header String connectionHdr = req.getHeader(HttpFields.__Connection); @@ -1559,6 +1571,10 @@ LinkRewriterFactory getLinkRewriterFactory(String mimeType) { return null; } + protected LockssUrlConnection openConnection(String url, LockssUrlConnectionPool pool) throws IOException { + return UrlUtil.openConnection(url, pool); + } + protected void handleRewriteInputStream(InputStream original, String mimeType, String charset, @@ -2151,12 +2167,18 @@ protected Collection getCandidateAus( protected void handleMissingUrlRequest(String missingUrl, PubState pstate) throws IOException { + handleUrlRequestError(missingUrl, pstate, "The requested URL is not preserved on this LOCKSS box. ", HttpResponse.__404_Not_Found, "not present"); + } + + protected void handleUrlRequestError(String missingUrl, PubState pstate, String errorMessage, int responseCode, String logMessage) + throws IOException { String missing = missingUrl + ((au != null) ? " in AU: " + au.getName() : ""); Block block = new Block(Block.Center); // display publisher page - block.add("

The requested URL is not preserved on this LOCKSS box. "); + block.add("

"); + block.add(errorMessage); block.add("Select link"); block.add(addFootnote( "Selecting publisher link takes you away from this LOCKSS box.")); @@ -2165,9 +2187,9 @@ protected void handleMissingUrlRequest(String missingUrl, PubState pstate) switch (getMissingFileAction(pstate)) { case Error_404: - resp.sendError(HttpServletResponse.SC_NOT_FOUND, + resp.sendError(responseCode, missing + " is not preserved on this LOCKSS box"); - logAccess("not present, 404"); + logAccess(logMessage + ", " + responseCode); break; case Redirect: case AlwaysRedirect: @@ -2183,24 +2205,24 @@ protected void handleMissingUrlRequest(String missingUrl, PubState pstate) } if (candidateAus != null && !candidateAus.isEmpty()) { displayIndexPage(candidateAus, - HttpResponse.__404_Not_Found, + responseCode, block, candidates404Msg); - logAccess("not present, 404 with index"); + logAccess(logMessage + ", " + responseCode + " with index"); } else { displayIndexPage(Collections.emptyList(), - HttpResponse.__404_Not_Found, + responseCode, block, null); - logAccess("not present, 404"); + logAccess(logMessage + ", " + responseCode); } break; case AuIndex: displayIndexPage(pluginMgr.getAllAus(), - HttpResponse.__404_Not_Found, + responseCode, block, null); - logAccess("not present, 404 with index"); + logAccess(logMessage + ", " + responseCode + " with index"); break; } } diff --git a/src/org/lockss/servlet/ServletUtil.java b/src/org/lockss/servlet/ServletUtil.java index ad1cb771f50..05ef59f4c26 100644 --- a/src/org/lockss/servlet/ServletUtil.java +++ b/src/org/lockss/servlet/ServletUtil.java @@ -373,6 +373,9 @@ protected String getLink() { public static void setConfig(Configuration config, Configuration oldConfig, Configuration.Differences diffs) { + if (diffs.contains(EntitlementCheckServeContent.PREFIX)) { + EntitlementCheckServeContent.setConfig(config, oldConfig, diffs); + } if (diffs.contains(ServeContent.PREFIX)) { ServeContent.setConfig(config, oldConfig, diffs); } diff --git a/test/src/org/lockss/safenet/TestCachingEntitlementRegistryClient.java b/test/src/org/lockss/entitlement/TestCachingEntitlementRegistryClient.java similarity index 65% rename from test/src/org/lockss/safenet/TestCachingEntitlementRegistryClient.java rename to test/src/org/lockss/entitlement/TestCachingEntitlementRegistryClient.java index 06b7e25674d..4d1b2a82700 100644 --- a/test/src/org/lockss/safenet/TestCachingEntitlementRegistryClient.java +++ b/test/src/org/lockss/entitlement/TestCachingEntitlementRegistryClient.java @@ -1,24 +1,29 @@ -package org.lockss.safenet; +package org.lockss.entitlement; import java.io.IOException; +import java.util.Properties; import org.mockito.Mockito; -import org.lockss.safenet.PublisherWorkflow; +import org.lockss.test.ConfigurationUtil; import org.lockss.test.LockssTestCase; import org.lockss.test.MockLockssDaemon; public class TestCachingEntitlementRegistryClient extends LockssTestCase { private EntitlementRegistryClient client; - private BaseEntitlementRegistryClient mock; + private EntitlementRegistryClient mock; public void setUp() throws Exception { super.setUp(); - mock = Mockito.mock(BaseEntitlementRegistryClient.class); - client = new CachingEntitlementRegistryClient(mock, 5); + mock = Mockito.mock(EntitlementRegistryClient.class); + client = new CachingEntitlementRegistryClient(); MockLockssDaemon daemon = getMockLockssDaemon(); - daemon.setEntitlementRegistryClient(client); + daemon.setEntitlementRegistryClient(mock); + daemon.setCachedEntitlementRegistryClient(client); daemon.setDaemonInited(true); + Properties p = new Properties(); + p.setProperty(CachingEntitlementRegistryClient.PARAM_CACHE_SIZE, "5"); + ConfigurationUtil.setCurrentConfigFromProps(p); client.initService(daemon); client.startService(); } @@ -27,9 +32,9 @@ public void testCaching() throws Exception { Mockito.when(mock.isUserEntitled("0000-0000", "11111111-1111-1111-1111-111111111111", "20120101", "20151231")).thenReturn(true); Mockito.when(mock.isUserEntitled("0000-0001", "11111111-1111-1111-1111-111111111111", "20120101", "20151231")).thenReturn(false); Mockito.when(mock.isUserEntitled("0000-0002", "11111111-1111-1111-1111-111111111111", "20120101", "20151231")).thenThrow(new IOException("Error communicating with entitlement registry. Response was 500 null")); - Mockito.when(mock.getPublisher("0000-0000", "20120101", "20151231")).thenReturn("33333333-0000-0000-0000-000000000000"); - Mockito.when(mock.getPublisher("0000-0001", "20120101", "20151231")).thenReturn("33333333-0000-0000-0000-000000000001"); - Mockito.when(mock.getPublisherWorkflow("33333333-0000-0000-0000-000000000000")).thenReturn(PublisherWorkflow.PRIMARY_SAFENET); + Mockito.when(mock.getPublisher("0000-0000", "11111111-1111-1111-1111-111111111111", "20120101", "20151231")).thenReturn("33333333-0000-0000-0000-000000000000"); + Mockito.when(mock.getPublisher("0000-0001", "11111111-1111-1111-1111-111111111111", "20120101", "20151231")).thenReturn("33333333-0000-0000-0000-000000000001"); + Mockito.when(mock.getPublisherWorkflow("33333333-0000-0000-0000-000000000000")).thenReturn(PublisherWorkflow.PRIMARY_LOCKSS); Mockito.when(mock.getPublisherWorkflow("33333333-1111-1111-1111-111111111111")).thenReturn(PublisherWorkflow.PRIMARY_PUBLISHER); assertTrue(client.isUserEntitled("0000-0000", "11111111-1111-1111-1111-111111111111", "20120101", "20151231")); @@ -41,15 +46,15 @@ public void testCaching() throws Exception { catch(IOException e) { assertEquals("Error communicating with entitlement registry. Response was 500 null", e.getMessage()); } - assertEquals("33333333-0000-0000-0000-000000000000", client.getPublisher("0000-0000", "20120101", "20151231")); - assertEquals("33333333-0000-0000-0000-000000000001", client.getPublisher("0000-0001", "20120101", "20151231")); - assertEquals(PublisherWorkflow.PRIMARY_SAFENET, client.getPublisherWorkflow("33333333-0000-0000-0000-000000000000")); + assertEquals("33333333-0000-0000-0000-000000000000", client.getPublisher("0000-0000", "11111111-1111-1111-1111-111111111111", "20120101", "20151231")); + assertEquals("33333333-0000-0000-0000-000000000001", client.getPublisher("0000-0001", "11111111-1111-1111-1111-111111111111", "20120101", "20151231")); + assertEquals(PublisherWorkflow.PRIMARY_LOCKSS, client.getPublisherWorkflow("33333333-0000-0000-0000-000000000000")); assertEquals(PublisherWorkflow.PRIMARY_PUBLISHER, client.getPublisherWorkflow("33333333-1111-1111-1111-111111111111")); assertEquals(PublisherWorkflow.PRIMARY_PUBLISHER, client.getPublisherWorkflow("33333333-1111-1111-1111-111111111111")); - assertEquals(PublisherWorkflow.PRIMARY_SAFENET, client.getPublisherWorkflow("33333333-0000-0000-0000-000000000000")); - assertEquals("33333333-0000-0000-0000-000000000001", client.getPublisher("0000-0001", "20120101", "20151231")); - assertEquals("33333333-0000-0000-0000-000000000000", client.getPublisher("0000-0000", "20120101", "20151231")); + assertEquals(PublisherWorkflow.PRIMARY_LOCKSS, client.getPublisherWorkflow("33333333-0000-0000-0000-000000000000")); + assertEquals("33333333-0000-0000-0000-000000000001", client.getPublisher("0000-0001", "11111111-1111-1111-1111-111111111111", "20120101", "20151231")); + assertEquals("33333333-0000-0000-0000-000000000000", client.getPublisher("0000-0000", "11111111-1111-1111-1111-111111111111", "20120101", "20151231")); try { client.isUserEntitled("0000-0002", "11111111-1111-1111-1111-111111111111", "20120101", "20151231"); fail("Expected exception not thrown"); @@ -63,8 +68,8 @@ public void testCaching() throws Exception { Mockito.verify(mock, Mockito.times(2)).isUserEntitled("0000-0000", "11111111-1111-1111-1111-111111111111", "20120101", "20151231"); Mockito.verify(mock).isUserEntitled("0000-0001", "11111111-1111-1111-1111-111111111111", "20120101", "20151231"); Mockito.verify(mock, Mockito.times(2)).isUserEntitled("0000-0002", "11111111-1111-1111-1111-111111111111", "20120101", "20151231"); - Mockito.verify(mock).getPublisher("0000-0000", "20120101", "20151231"); - Mockito.verify(mock).getPublisher("0000-0001", "20120101", "20151231"); + Mockito.verify(mock).getPublisher("0000-0000", "11111111-1111-1111-1111-111111111111", "20120101", "20151231"); + Mockito.verify(mock).getPublisher("0000-0001", "11111111-1111-1111-1111-111111111111", "20120101", "20151231"); Mockito.verify(mock).getPublisherWorkflow("33333333-0000-0000-0000-000000000000"); Mockito.verify(mock).getPublisherWorkflow("33333333-1111-1111-1111-111111111111"); } diff --git a/test/src/org/lockss/entitlement/TestKeepsafeEntitlementRegistryClient.java b/test/src/org/lockss/entitlement/TestKeepsafeEntitlementRegistryClient.java new file mode 100644 index 00000000000..0d807a1a512 --- /dev/null +++ b/test/src/org/lockss/entitlement/TestKeepsafeEntitlementRegistryClient.java @@ -0,0 +1,258 @@ +package org.lockss.entitlement; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.Properties; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.http.NameValuePair; +import org.apache.http.client.utils.URIBuilder; +import org.apache.http.message.BasicNameValuePair; +import org.mockito.Mockito; + +import org.lockss.test.ConfigurationUtil; +import org.lockss.test.LockssTestCase; +import org.lockss.test.MockLockssDaemon; +import org.lockss.test.MockLockssUrlConnection; +import org.lockss.test.StringInputStream; +import org.lockss.util.urlconn.LockssUrlConnection; + +public class TestKeepsafeEntitlementRegistryClient extends LockssTestCase { + private static final String ER_URI = "http://keepsafe.org"; + private KeepsafeEntitlementRegistryClient client; + + private Map validEntitlementParams; + private Map validResponseParams; + private Map validPublisherParams; + + public void setUp() throws Exception { + super.setUp(); + client = Mockito.spy(new KeepsafeEntitlementRegistryClient()); + MockLockssDaemon daemon = getMockLockssDaemon(); + daemon.setEntitlementRegistryClient(client); + daemon.setDaemonInited(true); + Properties p = new Properties(); + p.setProperty(KeepsafeEntitlementRegistryClient.PARAM_ER_URI, ER_URI); + p.setProperty(KeepsafeEntitlementRegistryClient.PARAM_ER_APIKEY, "00000000-0000-0000-0000-000000000000"); + ConfigurationUtil.setCurrentConfigFromProps(p); + client.initService(daemon); + client.startService(); + + validEntitlementParams = new HashMap(); + validEntitlementParams.put("identifier_value", "0123-456X"); + validEntitlementParams.put("institution", "11111111-1111-1111-1111-111111111111"); + validEntitlementParams.put("start", "20120101"); + validEntitlementParams.put("end", "20151231"); + validEntitlementParams.put("validate", "1"); + + validResponseParams = new HashMap(validEntitlementParams); + validResponseParams.put("institution", ER_URI + "/institutions/11111111-1111-1111-1111-111111111111/"); + validResponseParams.put("publisher", ER_URI + "/publishers/33333333-0000-0000-0000-000000000000/"); + + validPublisherParams = new HashMap(); + validPublisherParams.put("guid", "33333333-0000-0000-0000-000000000000"); + validPublisherParams.put("name", "Wiley"); + } + + public void testEntitlementRegistryError() throws Exception { + String url = url("/entitlements", KeepsafeEntitlementRegistryClient.mapToPairs(validEntitlementParams)); + Mockito.doReturn(connection(url, 500, "Internal server error")).when(client).openConnection(url); + + try { + client.isUserEntitled("0123-456X", "11111111-1111-1111-1111-111111111111", "20120101", "20151231"); + fail("Expected exception not thrown"); + } + catch(IOException e) { + assertEquals("Error communicating with entitlement registry. Response was 500 null", e.getMessage()); + } + Mockito.verify(client).openConnection(url); + } + + public void testEntitlementRegistryInvalidResponse() throws Exception { + String url = url("/entitlements", KeepsafeEntitlementRegistryClient.mapToPairs(validEntitlementParams)); + Mockito.doReturn(connection(url, 200, "[]")).when(client).openConnection(url); + + assertFalse(client.isUserEntitled("0123-456X", "11111111-1111-1111-1111-111111111111", "20120101", "20151231")); + Mockito.verify(client).openConnection(url); + } + + public void testEntitlementRegistryInvalidJson() throws Exception { + String url = url("/entitlements", KeepsafeEntitlementRegistryClient.mapToPairs(validEntitlementParams)); + Mockito.doReturn(connection(url, 200, "[{\"this\": isn't, JSON}]")).when(client).openConnection(url); + + try { + client.isUserEntitled("0123-456X", "11111111-1111-1111-1111-111111111111", "20120101", "20151231"); + fail("Expected exception not thrown"); + } + catch(IOException e) { + assertTrue(e.getMessage().startsWith("Unrecognized token 'isn': was expecting ('true', 'false' or 'null')")); + } + Mockito.verify(client).openConnection(url); + } + + public void testEntitlementRegistryUnexpectedJson() throws Exception { + String url = url("/entitlements", KeepsafeEntitlementRegistryClient.mapToPairs(validEntitlementParams)); + Mockito.doReturn(connection(url, 200, "{\"surprise\": \"object\"}")).when(client).openConnection(url); + + assertFalse(client.isUserEntitled("0123-456X", "11111111-1111-1111-1111-111111111111", "20120101", "20151231")); + Mockito.verify(client).openConnection(url); + } + + public void testUserEntitled() throws Exception { + String url = url("/entitlements", KeepsafeEntitlementRegistryClient.mapToPairs(validEntitlementParams)); + Mockito.doReturn(connection(url, 200, "[" + mapToJson(validResponseParams) + "]")).when(client).openConnection(url); + + assertTrue(client.isUserEntitled("0123-456X", "11111111-1111-1111-1111-111111111111", "20120101", "20151231")); + Mockito.verify(client).openConnection(url); + } + + public void testUserNotEntitled() throws Exception { + String url = url("/entitlements", KeepsafeEntitlementRegistryClient.mapToPairs(validEntitlementParams)); + Mockito.doReturn(connection(url, 204, "")).when(client).openConnection(url); + + assertFalse(client.isUserEntitled("0123-456X", "11111111-1111-1111-1111-111111111111", "20120101", "20151231")); + Mockito.verify(client).openConnection(url); + } + + public void testGetInstitution() throws Exception { + Map queryParams = new HashMap(); + queryParams.put("scope", "ed.ac.uk"); + Map institution = new HashMap(); + institution.put("guid", "11111111-0000-0000-0000-000000000000"); + institution.put("name", "University of Edinburgh"); + institution.put("scope", "ed.ac.uk"); + + String url = url("/institutions", KeepsafeEntitlementRegistryClient.mapToPairs(queryParams)); + Mockito.doReturn(connection(url, 200, "[" + mapToJson(institution) + "]")).when(client).openConnection(url); + assertEquals("11111111-0000-0000-0000-000000000000", client.getInstitution("ed.ac.uk")); + + Mockito.verify(client).openConnection(url); + } + + public void testGetInstitutionNoResponse() throws Exception { + Map queryParams = new HashMap(); + queryParams.put("scope", "ed.ac.uk"); + + String url = url("/institutions", KeepsafeEntitlementRegistryClient.mapToPairs(queryParams)); + Mockito.doReturn(connection(url, 200, "[]")).when(client).openConnection(url); + try { + client.getInstitution("ed.ac.uk"); + fail("Expected exception not thrown"); + } + catch (IOException e) { + assertEquals("No matching institutions returned from entitlement registry", e.getMessage()); + } + + Mockito.verify(client).openConnection(url); + } + + public void testGetInstitutionMultipleResults() throws Exception { + Map queryParams = new HashMap(); + queryParams.put("scope", "ed.ac.uk"); + Map institution1 = new HashMap(); + institution1.put("id", "11111111-0000-0000-0000-000000000000"); + institution1.put("name", "University of Edinburgh"); + institution1.put("scope", "ed.ac.uk"); + Map institution2 = new HashMap(); + institution2.put("id", "11111111-1111-1111-1111-111111111111"); + institution2.put("name", "University of Edinburgh 2"); + institution2.put("scope", "ed.ac.uk"); + + String url = url("/institutions", KeepsafeEntitlementRegistryClient.mapToPairs(queryParams)); + Mockito.doReturn(connection(url, 200, "[" + mapToJson(institution1) + "," + mapToJson(institution2) + "]")).when(client).openConnection(url); + try { + client.getInstitution("ed.ac.uk"); + fail("Expected exception not thrown"); + } + catch (IOException e) { + assertEquals("Multiple matching institutions returned from entitlement registry", e.getMessage()); + } + + Mockito.verify(client).openConnection(url); + } + + public void testGetPublisher() throws Exception { + String url = url("/entitlements", KeepsafeEntitlementRegistryClient.mapToPairs(validEntitlementParams)); + Mockito.doReturn(connection(url, 200, "[" + mapToJson(validResponseParams) + "]")).when(client).openConnection(url); + + assertEquals("33333333-0000-0000-0000-000000000000", client.getPublisher("0123-456X", "11111111-1111-1111-1111-111111111111", "20120101", "20151231")); + Mockito.verify(client).openConnection(url); + } + + public void testGetPublisherNotEntitled() throws Exception { + String url = url("/entitlements", KeepsafeEntitlementRegistryClient.mapToPairs(validEntitlementParams)); + Mockito.doReturn(connection(url, 204, "")).when(client).openConnection(url); + + assertEquals(null, client.getPublisher("0123-456X", "11111111-1111-1111-1111-111111111111", "20120101", "20151231")); + Mockito.verify(client).openConnection(url); + } + + public void testGetPublisherWorkflow() throws Exception { + Map responseParams = new HashMap(validPublisherParams); + responseParams.put("workflow", "primary_lockss"); + String url = url("/publishers/33333333-0000-0000-0000-000000000000"); + Mockito.doReturn(connection(url, 200, mapToJson(responseParams))).when(client).openConnection(url); + + assertEquals(PublisherWorkflow.PRIMARY_LOCKSS, client.getPublisherWorkflow("33333333-0000-0000-0000-000000000000")); + Mockito.verify(client).openConnection(url); + } + + public void testGetPublisherWorkflowMissingWorkflow() throws Exception { + Map responseParams = new HashMap(validPublisherParams); + String url = url("/publishers/33333333-0000-0000-0000-000000000000"); + Mockito.doReturn(connection(url, 200, mapToJson(responseParams))).when(client).openConnection(url); + + assertEquals(PublisherWorkflow.PRIMARY_LOCKSS, client.getPublisherWorkflow("33333333-0000-0000-0000-000000000000")); + Mockito.verify(client).openConnection(url); + } + + public void testGetPublisherWorkflowInvalidWorkflow() throws Exception { + Map responseParams = new HashMap(validPublisherParams); + responseParams.put("workflow", "gibberish"); + String url = url("/publishers/33333333-0000-0000-0000-000000000000"); + Mockito.doReturn(connection(url, 200, mapToJson(responseParams))).when(client).openConnection(url); + + try { + client.getPublisherWorkflow("33333333-0000-0000-0000-000000000000"); + fail("Expected exception not thrown"); + } + catch(IOException e) { + assertTrue(e.getMessage().startsWith("No valid workflow returned from entitlement registry")); + } + Mockito.verify(client).openConnection(url); + } + + private String url(String endpoint) { + return url(endpoint, null); + } + + private String url(String endpoint, List params) { + URIBuilder builder = new URIBuilder(); + builder.setScheme("http"); + builder.setHost("keepsafe.org"); + builder.setPath(endpoint); + if(params != null) { + builder.setParameters(params); + } + return builder.toString(); + } + + private MockLockssUrlConnection connection(String url, int responseCode, String response) throws IOException { + MockLockssUrlConnection connection = new MockLockssUrlConnection(url); + connection.setResponseCode(responseCode); + connection.setResponseInputStream(new StringInputStream(response)); + return connection; + } + + private String mapToJson(Map params) throws IOException { + ObjectMapper mapper = new ObjectMapper(); + return mapper.writeValueAsString(params); + } +} + diff --git a/test/src/org/lockss/safenet/TestEntitlementRegistryClient.java b/test/src/org/lockss/safenet/TestEntitlementRegistryClient.java deleted file mode 100644 index 52499d0b246..00000000000 --- a/test/src/org/lockss/safenet/TestEntitlementRegistryClient.java +++ /dev/null @@ -1,409 +0,0 @@ -package org.lockss.safenet; - -import java.io.IOException; -import java.net.URISyntaxException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Queue; -import java.util.Properties; - -import com.fasterxml.jackson.databind.ObjectMapper; -import org.apache.http.NameValuePair; -import org.apache.http.client.utils.URIBuilder; -import org.apache.http.message.BasicNameValuePair; -import org.mockito.Mockito; - -import org.lockss.safenet.PublisherWorkflow; -import org.lockss.test.ConfigurationUtil; -import org.lockss.test.LockssTestCase; -import org.lockss.test.MockLockssDaemon; -import org.lockss.test.MockLockssUrlConnection; -import org.lockss.test.StringInputStream; -import org.lockss.util.urlconn.LockssUrlConnection; - -public class TestEntitlementRegistryClient extends LockssTestCase { - private BaseEntitlementRegistryClient client; - - private Map validEntitlementParams; - private Map validPublisherParams; - - public void setUp() throws Exception { - super.setUp(); - client = Mockito.spy(new BaseEntitlementRegistryClient()); - MockLockssDaemon daemon = getMockLockssDaemon(); - daemon.setEntitlementRegistryClient(client); - daemon.setDaemonInited(true); - Properties p = new Properties(); - p.setProperty(BaseEntitlementRegistryClient.PARAM_ER_URI, "http://dev-safenet.edina.ac.uk"); - p.setProperty(BaseEntitlementRegistryClient.PARAM_ER_APIKEY, "00000000-0000-0000-0000-000000000000"); - ConfigurationUtil.setCurrentConfigFromProps(p); - client.initService(daemon); - client.startService(); - - validEntitlementParams = new HashMap(); - validEntitlementParams.put("api_key", "00000000-0000-0000-0000-000000000000"); - validEntitlementParams.put("identifier_value", "0123-456X"); - validEntitlementParams.put("institution", "11111111-1111-1111-1111-111111111111"); - validEntitlementParams.put("start", "20120101"); - validEntitlementParams.put("end", "20151231"); - - validPublisherParams = new HashMap(); - validPublisherParams.put("id", "33333333-0000-0000-0000-000000000000"); - validPublisherParams.put("name", "Wiley"); - } - - public void testEntitlementRegistryError() throws Exception { - String url = url("/entitlements", BaseEntitlementRegistryClient.mapToPairs(validEntitlementParams)); - Mockito.doReturn(connection(url, 500, "Internal server error")).when(client).openConnection(url); - - try { - client.isUserEntitled("0123-456X", "11111111-1111-1111-1111-111111111111", "20120101", "20151231"); - fail("Expected exception not thrown"); - } - catch(IOException e) { - assertEquals("Error communicating with entitlement registry. Response was 500 null", e.getMessage()); - } - Mockito.verify(client).openConnection(url); - } - - public void testEntitlementRegistryInvalidResponse() throws Exception { - String url = url("/entitlements", BaseEntitlementRegistryClient.mapToPairs(validEntitlementParams)); - Mockito.doReturn(connection(url, 200, "[]")).when(client).openConnection(url); - - try { - client.isUserEntitled("0123-456X", "11111111-1111-1111-1111-111111111111", "20120101", "20151231"); - fail("Expected exception not thrown"); - } - catch(IOException e) { - assertEquals("No matching entitlements returned from entitlement registry", e.getMessage()); - } - Mockito.verify(client).openConnection(url); - } - - public void testEntitlementRegistryInvalidJson() throws Exception { - String url = url("/entitlements", BaseEntitlementRegistryClient.mapToPairs(validEntitlementParams)); - Mockito.doReturn(connection(url, 200, "[{\"this\": isn't, JSON}]")).when(client).openConnection(url); - - try { - client.isUserEntitled("0123-456X", "11111111-1111-1111-1111-111111111111", "20120101", "20151231"); - fail("Expected exception not thrown"); - } - catch(IOException e) { - assertTrue(e.getMessage().startsWith("Unrecognized token 'isn': was expecting ('true', 'false' or 'null')")); - } - Mockito.verify(client).openConnection(url); - } - - public void testEntitlementRegistryUnexpectedJson() throws Exception { - String url = url("/entitlements", BaseEntitlementRegistryClient.mapToPairs(validEntitlementParams)); - Mockito.doReturn(connection(url, 200, "{\"surprise\": \"object\"}")).when(client).openConnection(url); - - try { - client.isUserEntitled("0123-456X", "11111111-1111-1111-1111-111111111111", "20120101", "20151231"); - fail("Expected exception not thrown"); - } - catch(IOException e) { - assertTrue(e.getMessage().startsWith("No matching entitlements returned from entitlement registry")); - } - Mockito.verify(client).openConnection(url); - } - - public void testUserEntitled() throws Exception { - Map responseParams = new HashMap(validEntitlementParams); - responseParams.remove("api_key"); - String url = url("/entitlements", BaseEntitlementRegistryClient.mapToPairs(validEntitlementParams)); - Mockito.doReturn(connection(url, 200, "[" + mapToJson(responseParams) + "]")).when(client).openConnection(url); - - assertTrue(client.isUserEntitled("0123-456X", "11111111-1111-1111-1111-111111111111", "20120101", "20151231")); - Mockito.verify(client).openConnection(url); - } - - public void testUserNotEntitled() throws Exception { - String url = url("/entitlements", BaseEntitlementRegistryClient.mapToPairs(validEntitlementParams)); - Mockito.doReturn(connection(url, 204, "")).when(client).openConnection(url); - - assertFalse(client.isUserEntitled("0123-456X", "11111111-1111-1111-1111-111111111111", "20120101", "20151231")); - Mockito.verify(client).openConnection(url); - } - - public void testGetInstitution() throws Exception { - Map queryParams = new HashMap(); - queryParams.put("scope", "ed.ac.uk"); - Map institution = new HashMap(); - institution.put("id", "11111111-0000-0000-0000-000000000000"); - institution.put("name", "University of Edinburgh"); - institution.put("scope", "ed.ac.uk"); - - String url = url("/institutions", BaseEntitlementRegistryClient.mapToPairs(queryParams)); - Mockito.doReturn(connection(url, 200, "[" + mapToJson(institution) + "]")).when(client).openConnection(url); - assertEquals("11111111-0000-0000-0000-000000000000", client.getInstitution("ed.ac.uk")); - - Mockito.verify(client).openConnection(url); - } - - public void testGetInstitutionNoResponse() throws Exception { - Map queryParams = new HashMap(); - queryParams.put("scope", "ed.ac.uk"); - - String url = url("/institutions", BaseEntitlementRegistryClient.mapToPairs(queryParams)); - Mockito.doReturn(connection(url, 200, "[]")).when(client).openConnection(url); - try { - client.getInstitution("ed.ac.uk"); - fail("Expected exception not thrown"); - } - catch (IOException e) { - assertEquals("No matching institutions returned from entitlement registry", e.getMessage()); - } - - Mockito.verify(client).openConnection(url); - } - - public void testGetInstitutionMultipleResults() throws Exception { - Map queryParams = new HashMap(); - queryParams.put("scope", "ed.ac.uk"); - Map institution1 = new HashMap(); - institution1.put("id", "11111111-0000-0000-0000-000000000000"); - institution1.put("name", "University of Edinburgh"); - institution1.put("scope", "ed.ac.uk"); - Map institution2 = new HashMap(); - institution2.put("id", "11111111-1111-1111-1111-111111111111"); - institution2.put("name", "University of Edinburgh 2"); - institution2.put("scope", "ed.ac.uk"); - - String url = url("/institutions", BaseEntitlementRegistryClient.mapToPairs(queryParams)); - Mockito.doReturn(connection(url, 200, "[" + mapToJson(institution1) + "," + mapToJson(institution2) + "]")).when(client).openConnection(url); - try { - client.getInstitution("ed.ac.uk"); - fail("Expected exception not thrown"); - } - catch (IOException e) { - assertEquals("Multiple matching institutions returned from entitlement registry", e.getMessage()); - } - - Mockito.verify(client).openConnection(url); - } - - public void testGetPublisher() throws Exception { - Map queryParams = new HashMap(); - queryParams.put("identifier", "0123-456X"); - Map publisher = new HashMap(); - publisher.put("id", "33333333-0000-0000-0000-000000000000"); - publisher.put("start", null); - publisher.put("end", null); - List> publishers = new ArrayList>(); - publishers.add(publisher); - Map responseParams = new HashMap(); - responseParams.put("publishers", publishers); - - String url = url("/titles", BaseEntitlementRegistryClient.mapToPairs(queryParams)); - Mockito.doReturn(connection(url, 200, "[" + mapToJson(responseParams) + "]")).when(client).openConnection(url); - assertEquals("33333333-0000-0000-0000-000000000000", client.getPublisher("0123-456X", "20120101", "20151231")); - - Mockito.doReturn(connection(url, 200, "[" + mapToJson(responseParams) + "]")).when(client).openConnection(url); - assertEquals("33333333-0000-0000-0000-000000000000", client.getPublisher("0123-456X", null, "20151231")); - - Mockito.doReturn(connection(url, 200, "[" + mapToJson(responseParams) + "]")).when(client).openConnection(url); - assertEquals("33333333-0000-0000-0000-000000000000", client.getPublisher("0123-456X", "20120101", null)); - - Mockito.doReturn(connection(url, 200, "[" + mapToJson(responseParams) + "]")).when(client).openConnection(url); - assertEquals("33333333-0000-0000-0000-000000000000", client.getPublisher("0123-456X", null, null)); - - Mockito.verify(client, Mockito.times(4)).openConnection(url); - } - - public void testGetPublisherDateLimited() throws Exception { - Map queryParams = new HashMap(); - queryParams.put("identifier", "0123-456X"); - Map publisher = new HashMap(); - publisher.put("id", "33333333-0000-0000-0000-000000000000"); - publisher.put("start", "20120101"); - publisher.put("end", "20151231"); - - List> publishers = new ArrayList>(); - publishers.add(publisher); - Map responseParams = new HashMap(); - responseParams.put("publishers", publishers); - - String url = url("/titles", BaseEntitlementRegistryClient.mapToPairs(queryParams)); - Mockito.doReturn(connection(url, 200, "[" + mapToJson(responseParams) + "]")).when(client).openConnection(url); - assertEquals("33333333-0000-0000-0000-000000000000", client.getPublisher("0123-456X", "20120101", "20151231")); - - Mockito.doReturn(connection(url, 200, "[" + mapToJson(responseParams) + "]")).when(client).openConnection(url); - assertEquals(null, client.getPublisher("0123-456X", "20111231", "20151231")); - - Mockito.doReturn(connection(url, 200, "[" + mapToJson(responseParams) + "]")).when(client).openConnection(url); - assertEquals(null, client.getPublisher("0123-456X", "20120101", "20160101")); - - Mockito.doReturn(connection(url, 200, "[" + mapToJson(responseParams) + "]")).when(client).openConnection(url); - assertEquals("33333333-0000-0000-0000-000000000000", client.getPublisher("0123-456X", "20120102", "20151230")); - - Mockito.doReturn(connection(url, 200, "[" + mapToJson(responseParams) + "]")).when(client).openConnection(url); - assertEquals(null, client.getPublisher("0123-456X", null, "20151231")); - - Mockito.doReturn(connection(url, 200, "[" + mapToJson(responseParams) + "]")).when(client).openConnection(url); - assertEquals(null, client.getPublisher("0123-456X", "20120101", null)); - - Mockito.doReturn(connection(url, 200, "[" + mapToJson(responseParams) + "]")).when(client).openConnection(url); - assertEquals(null, client.getPublisher("0123-456X", null, null)); - - Mockito.verify(client, Mockito.times(7)).openConnection(url); - } - - public void testGetPublisherNoResponse() throws Exception { - Map queryParams = new HashMap(); - queryParams.put("identifier", "0123-456X"); - String url = url("/titles", BaseEntitlementRegistryClient.mapToPairs(queryParams)); - Mockito.doReturn(connection(url, 200, "[]")).when(client).openConnection(url); - assertEquals(null, client.getPublisher("0123-456X", "20120101", "20151231")); - } - - private List> getMultiplePublishers() { - Map publisher = new HashMap(); - publisher.put("id", "33333333-0000-0000-0000-000000000000"); - publisher.put("start", "20120101"); - publisher.put("end", "20151231"); - Map publisher2 = new HashMap(); - publisher2.put("id", "33333333-1111-1111-1111-111111111111"); - publisher2.put("start", "20160101"); - publisher2.put("end", null); - List> publishers = new ArrayList>(); - publishers.add(publisher); - publishers.add(publisher2); - return publishers; - } - - public void testGetPublisherMultipleResponses() throws Exception { - Map queryParams = new HashMap(); - queryParams.put("identifier", "0123-456X"); - Map responseParams = new HashMap(); - List> publishers = getMultiplePublishers(); - responseParams.put("publishers", publishers); - - String url = url("/titles", BaseEntitlementRegistryClient.mapToPairs(queryParams)); - Mockito.doReturn(connection(url, 200, "[" + mapToJson(responseParams) + "]")).when(client).openConnection(url); - assertEquals("33333333-0000-0000-0000-000000000000", client.getPublisher("0123-456X", "20150101", "20151231")); - - Mockito.verify(client).openConnection(url); - } - - public void testGetPublisherMultipleResponses2() throws Exception { - Map queryParams = new HashMap(); - queryParams.put("identifier", "0123-456X"); - Map responseParams = new HashMap(); - List> publishers = getMultiplePublishers(); - responseParams.put("publishers", publishers); - - String url = url("/titles", BaseEntitlementRegistryClient.mapToPairs(queryParams)); - Mockito.doReturn(connection(url, 200, "[" + mapToJson(responseParams) + "]")).when(client).openConnection(url); - assertEquals("33333333-1111-1111-1111-111111111111", client.getPublisher("0123-456X", "20160101", "20161231")); - - Mockito.verify(client).openConnection(url); - } - - public void testGetPublisherMultipleResponsesOutsideRange() throws Exception { - Map queryParams = new HashMap(); - queryParams.put("identifier", "0123-456X"); - Map responseParams = new HashMap(); - List> publishers = getMultiplePublishers(); - responseParams.put("publishers", publishers); - - String url = url("/titles", BaseEntitlementRegistryClient.mapToPairs(queryParams)); - Mockito.doReturn(connection(url, 200, "[" + mapToJson(responseParams) + "]")).when(client).openConnection(url); - assertEquals(null, client.getPublisher("0123-456X", "20150101", "20161231")); - - Mockito.verify(client).openConnection(url); - } - - public void testGetPublisherMultipleResponsesMultipleMatching() throws Exception { - Map queryParams = new HashMap(); - queryParams.put("identifier", "0123-456X"); - Map responseParams = new HashMap(); - List> publishers = getMultiplePublishers(); - responseParams.put("publishers", publishers); - - publishers.get(1).put("start", null); - String url = url("/titles", BaseEntitlementRegistryClient.mapToPairs(queryParams)); - Mockito.doReturn(connection(url, 200, "[" + mapToJson(responseParams) + "]")).when(client).openConnection(url); - try { - client.getPublisher("0123-456X", "20150101", "20151231"); - fail("Expected exception not thrown"); - } - catch (IOException e) { - assertEquals("Multiple matching publishers returned from entitlement registry", e.getMessage()); - } - - Mockito.verify(client).openConnection(url); - } - - public void testGetPublisherWorkflow() throws Exception { - Map responseParams = new HashMap(validPublisherParams); - responseParams.put("workflow", "primary_safenet"); - String url = url("/publishers/33333333-0000-0000-0000-000000000000"); - Mockito.doReturn(connection(url, 200, mapToJson(responseParams))).when(client).openConnection(url); - - assertEquals(PublisherWorkflow.PRIMARY_SAFENET, client.getPublisherWorkflow("33333333-0000-0000-0000-000000000000")); - Mockito.verify(client).openConnection(url); - } - - public void testGetPublisherWorkflowMissingWorkflow() throws Exception { - Map responseParams = new HashMap(validPublisherParams); - String url = url("/publishers/33333333-0000-0000-0000-000000000000"); - Mockito.doReturn(connection(url, 200, mapToJson(responseParams))).when(client).openConnection(url); - - try { - client.getPublisherWorkflow("33333333-0000-0000-0000-000000000000"); - fail("Expected exception not thrown"); - } - catch(IOException e) { - assertTrue(e.getMessage().startsWith("No valid workflow returned from entitlement registry")); - } - Mockito.verify(client).openConnection(url); - } - - public void testGetPublisherWorkflowInvalidWorkflow() throws Exception { - Map responseParams = new HashMap(validPublisherParams); - responseParams.put("workflow", "gibberish"); - String url = url("/publishers/33333333-0000-0000-0000-000000000000"); - Mockito.doReturn(connection(url, 200, mapToJson(responseParams))).when(client).openConnection(url); - - try { - client.getPublisherWorkflow("33333333-0000-0000-0000-000000000000"); - fail("Expected exception not thrown"); - } - catch(IOException e) { - assertTrue(e.getMessage().startsWith("No valid workflow returned from entitlement registry")); - } - Mockito.verify(client).openConnection(url); - } - - private String url(String endpoint) { - return url(endpoint, null); - } - - private String url(String endpoint, List params) { - URIBuilder builder = new URIBuilder(); - builder.setScheme("http"); - builder.setHost("dev-safenet.edina.ac.uk"); - builder.setPath(endpoint); - if(params != null) { - builder.setParameters(params); - } - return builder.toString(); - } - - private MockLockssUrlConnection connection(String url, int responseCode, String response) throws IOException { - MockLockssUrlConnection connection = new MockLockssUrlConnection(url); - connection.setResponseCode(responseCode); - connection.setResponseInputStream(new StringInputStream(response)); - return connection; - } - - private String mapToJson(Map params) throws IOException { - ObjectMapper mapper = new ObjectMapper(); - return mapper.writeValueAsString(params); - } -} - diff --git a/test/src/org/lockss/servlet/TestEntitlementCheckServeContent.java b/test/src/org/lockss/servlet/TestEntitlementCheckServeContent.java new file mode 100644 index 00000000000..a60fa1c85ee --- /dev/null +++ b/test/src/org/lockss/servlet/TestEntitlementCheckServeContent.java @@ -0,0 +1,465 @@ +/* + * $Id$ + */ + +/* + +Copyright (c) 2000-2014 Board of Trustees of Leland Stanford Jr. University, +all rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +STANFORD UNIVERSITY BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Stanford University shall not +be used in advertising or otherwise to promote the sale, use or other dealings +in this Software without prior written authorization from Stanford University. + +*/ + +package org.lockss.servlet; + +import static org.lockss.db.SqlConstants.*; + +import java.io.IOException; +import java.io.InputStream; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.util.Arrays; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Queue; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.ServletException; + +import org.lockss.app.LockssDaemon; +import org.lockss.config.ConfigManager; +import org.lockss.config.Tdb; +import org.lockss.config.TdbAu; +import org.lockss.daemon.TitleConfig; +import org.lockss.db.DbManager; +import org.lockss.entitlement.EntitlementRegistryClient; +import org.lockss.entitlement.PublisherWorkflow; +import org.lockss.extractor.MetadataField; +import org.lockss.metadata.MetadataManager; +import org.lockss.plugin.ArchivalUnit; +import org.lockss.plugin.CachedUrl; +import org.lockss.plugin.Plugin; +import org.lockss.plugin.PluginManager; +import org.lockss.test.ConfigurationUtil; +import org.lockss.test.MockArchivalUnit; +import org.lockss.test.MockCachedUrl; +import org.lockss.test.MockLockssDaemon; +import org.lockss.test.MockLockssUrlConnection; +import org.lockss.test.MockNodeManager; +import org.lockss.test.MockPlugin; +import org.lockss.test.StringInputStream; +import org.lockss.util.Logger; +import org.lockss.util.urlconn.LockssUrlConnection; +import org.lockss.util.urlconn.LockssUrlConnectionPool; + +import com.meterware.httpunit.WebRequest; +import com.meterware.httpunit.WebResponse; +import com.meterware.httpunit.WebTable; +import com.meterware.httpunit.GetMethodWebRequest; +import com.meterware.servletunit.InvocationContext; +import org.mockito.Mockito; +import org.apache.commons.collections.map.MultiKeyMap; + +public class TestEntitlementCheckServeContent extends LockssServletTestCase { + + private MockPluginManager pluginMgr = null; + private EntitlementRegistryClient entitlementRegistryClient = null; + private DbManager dbManager; + private MetadataManager metadataManager; + + public void setUp() throws Exception { + super.setUp(); + String tempDirPath = setUpDiskSpace(); + pluginMgr = new MockPluginManager(theDaemon); + theDaemon.setPluginManager(pluginMgr); + theDaemon.setIdentityManager(new org.lockss.protocol.MockIdentityManager()); + entitlementRegistryClient = Mockito.mock(EntitlementRegistryClient.class); + theDaemon.setCachedEntitlementRegistryClient(entitlementRegistryClient); + theDaemon.getServletManager(); + theDaemon.setDaemonInited(true); + theDaemon.setAusStarted(true); + theDaemon.getRemoteApi().startService(); + + dbManager = getTestDbManager(tempDirPath); + + pluginMgr.initService(theDaemon); + pluginMgr.startService(); + + metadataManager = theDaemon.getMetadataManager(); + metadataManager.startService(); + + ConfigurationUtil.addFromArgs(LockssDaemon.PARAM_KEEPSAFE_ENABLED, "true"); + ConfigurationUtil.addFromArgs(EntitlementCheckServeContent.PARAM_MISSING_FILE_ACTION, "Redirect"); + ConfigurationUtil.addFromArgs(EntitlementCheckServeContent.PARAM_MOCK_SCOPE, "true"); + //ConfigurationUtil.addFromArgs("org.lockss.log.default.level", "debug3"); + } + + private MockArchivalUnit makeAu() throws Exception { + return makeAu(null); + } + + private MockArchivalUnit makeAu(Properties override) throws Exception { + Plugin plugin = new MockPlugin(theDaemon); + Tdb tdb = new Tdb(); + + // Tdb with values for some metadata fields + Properties tdbProps = new Properties(); + tdbProps.setProperty("title", "Air and Space Volume 1"); + tdbProps.setProperty("journalTitle", "Air and Space"); + tdbProps.setProperty("attributes.isbn", "976-1-58562-317-7"); + tdbProps.setProperty("issn", "0740-2783"); + tdbProps.setProperty("eissn", "0740-2783"); + tdbProps.setProperty("attributes.year", "2014"); + tdbProps.setProperty("attributes.publisher", "Wiley"); + tdbProps.setProperty("attributes.provider", "Provider[10.0135/12345678]"); + + tdbProps.setProperty("param.1.key", "base_url"); + tdbProps.setProperty("param.1.value", "http://publisher.org/test_journal/"); + tdbProps.setProperty("param.2.key", "volume"); + tdbProps.setProperty("param.2.value", "vol1"); + tdbProps.setProperty("plugin", plugin.getClass().toString()); + + if(override != null) { + tdbProps.putAll(override); + } + + TdbAu tdbAu = tdb.addTdbAuFromProperties(tdbProps); + TitleConfig titleConfig = new TitleConfig(tdbAu, plugin); + MockArchivalUnit au = new MockArchivalUnit(plugin, "TestAU"); + au.setStartUrls(Arrays.asList("http://publisher.org/test_journal/")); + au.setTitleConfig(titleConfig); + return au; + } + + protected void initServletRunner() { + super.initServletRunner(); + sRunner.setServletContextAttribute(ServletManager.CONTEXT_ATTR_SERVLET_MGR, new ContentServletManager()); + sRunner.registerServlet("/EntitlementCheckServeContent", MockEntitlementCheckServeContent.class.getName() ); + sRunner.registerServlet("/test_journal/", RedirectServlet.class.getName()); + } + + public void testIndex() throws Exception { + initServletRunner(); + pluginMgr.addAu(makeAu(), null); + WebRequest request = new GetMethodWebRequest("http://null/EntitlementCheckServeContent" ); + InvocationContext ic = sClient.newInvocation(request); + EntitlementCheckServeContent snsc = (EntitlementCheckServeContent) ic.getServlet(); + + WebResponse resp1 = sClient.getResponse(request); + assertResponseOk(resp1); + assertEquals("content type", "text/html", resp1.getContentType()); + WebTable auTable = resp1.getTableStartingWith("Archival Unit"); + assertNotNull(auTable); + assertEquals(2, auTable.getRowCount()); + assertEquals(3, auTable.getColumnCount()); + assertEquals("MockAU", auTable.getCellAsText(1, 0)); + assertEquals("/ServeContent?url=http%3A%2F%2Fpublisher.org%2Ftest_journal%2F&auid=TestAU", auTable.getTableCell(1, 1).getLinks()[0].getURLString()); + } + + public void testMissingUrl() throws Exception { + initServletRunner(); + pluginMgr.addAu(makeAu(), null); + sClient.setExceptionsThrownOnErrorStatus(false); + WebRequest request = new GetMethodWebRequest("http://null/EntitlementCheckServeContent?scope=ed.ac.uk&url=http%3A%2F%2Fpublisher.org%2Ftest_journal%2F" ); + InvocationContext ic = sClient.newInvocation(request); + EntitlementCheckServeContent snsc = (EntitlementCheckServeContent) ic.getServlet(); + + WebResponse resp1 = sClient.getResponse(request); + assertResponseOk(resp1); + assertEquals("BlahRedirected content", resp1.getText()); + } + + public void testMissingUrlExplicitAU() throws Exception { + initServletRunner(); + pluginMgr.addAu(makeAu(), null); + sClient.setExceptionsThrownOnErrorStatus(false); + WebRequest request = new GetMethodWebRequest("http://null/EntitlementCheckServeContent?scope=ed.ac.uk&url=http%3A%2F%2Fpublisher.org%2Ftest_journal%2F&auid=TestAU" ); + InvocationContext ic = sClient.newInvocation(request); + EntitlementCheckServeContent snsc = (EntitlementCheckServeContent) ic.getServlet(); + + WebResponse resp1 = sClient.getResponse(request); + assertResponseOk(resp1); + assertEquals("BlahRedirected content", resp1.getText()); + } + + public void testCachedUrlPrimaryPublisherResponse() throws Exception { + initServletRunner(); + pluginMgr.addAu(makeAu()); + Mockito.when(entitlementRegistryClient.getInstitution("ed.ac.uk")).thenReturn("03bd5fc6-97f0-11e4-b270-8932ea886a12"); + Mockito.when(entitlementRegistryClient.isUserEntitled("0740-2783", "03bd5fc6-97f0-11e4-b270-8932ea886a12", "20140101", "20141231")).thenReturn(true); + Mockito.when(entitlementRegistryClient.getPublisher("0740-2783", "03bd5fc6-97f0-11e4-b270-8932ea886a12", "20140101", "20141231")).thenReturn("33333333-0000-0000-0000-000000000000"); + Mockito.when(entitlementRegistryClient.getPublisherWorkflow("33333333-0000-0000-0000-000000000000")).thenReturn(PublisherWorkflow.PRIMARY_PUBLISHER); + WebRequest request = new GetMethodWebRequest("http://null/EntitlementCheckServeContent?scope=ed.ac.uk&url=http%3A%2F%2Fpublisher.org%2Ftest_journal%2F" ); + InvocationContext ic = sClient.newInvocation(request); + MockEntitlementCheckServeContent snsc = (MockEntitlementCheckServeContent) ic.getServlet(); + LockssUrlConnection connection = mockConnection(200, "BlahFetched content"); + snsc.expectRequest("http://publisher.org/test_journal/", connection); + + WebResponse resp1 = sClient.getResponse(request); + assertResponseOk(resp1); + assertEquals("BlahFetched content", resp1.getText()); + Mockito.verify(connection).addRequestProperty("X-Forwarded-For", "127.0.0.1"); + Mockito.verify(connection).addRequestProperty("X-Lockss-Institution", "ed.ac.uk"); + } + + public void testCachedUrlPrimaryPublisherError() throws Exception { + initServletRunner(); + pluginMgr.addAu(makeAu()); + Mockito.when(entitlementRegistryClient.getInstitution("ed.ac.uk")).thenReturn("03bd5fc6-97f0-11e4-b270-8932ea886a12"); + Mockito.when(entitlementRegistryClient.isUserEntitled("0740-2783", "03bd5fc6-97f0-11e4-b270-8932ea886a12", "20140101", "20141231")).thenReturn(true); + Mockito.when(entitlementRegistryClient.getPublisher("0740-2783", "03bd5fc6-97f0-11e4-b270-8932ea886a12", "20140101", "20141231")).thenReturn("33333333-0000-0000-0000-000000000000"); + Mockito.when(entitlementRegistryClient.getPublisherWorkflow("33333333-0000-0000-0000-000000000000")).thenReturn(PublisherWorkflow.PRIMARY_PUBLISHER); + WebRequest request = new GetMethodWebRequest("http://null/EntitlementCheckServeContent?scope=ed.ac.uk&url=http%3A%2F%2Fpublisher.org%2Ftest_journal%2F" ); + InvocationContext ic = sClient.newInvocation(request); + MockEntitlementCheckServeContent snsc = (MockEntitlementCheckServeContent) ic.getServlet(); + LockssUrlConnection connection = mockConnection(403, "BlahContent refused"); + snsc.expectRequest("http://publisher.org/test_journal/", connection); + + WebResponse resp1 = sClient.getResponse(request); + assertResponseOk(resp1); + assertEquals("BlahCached content", resp1.getText()); + Mockito.verify(connection).addRequestProperty("X-Forwarded-For", "127.0.0.1"); + Mockito.verify(connection).addRequestProperty("X-Lockss-Institution", "ed.ac.uk"); + } + + public void testCachedUrlPrimaryLockss() throws Exception { + initServletRunner(); + pluginMgr.addAu(makeAu()); + Mockito.when(entitlementRegistryClient.getInstitution("ed.ac.uk")).thenReturn("03bd5fc6-97f0-11e4-b270-8932ea886a12"); + Mockito.when(entitlementRegistryClient.isUserEntitled("0740-2783", "03bd5fc6-97f0-11e4-b270-8932ea886a12", "20140101", "20141231")).thenReturn(true); + Mockito.when(entitlementRegistryClient.getPublisher("0740-2783", "03bd5fc6-97f0-11e4-b270-8932ea886a12", "20140101", "20141231")).thenReturn("33333333-0000-0000-0000-000000000000"); + Mockito.when(entitlementRegistryClient.getPublisherWorkflow("33333333-0000-0000-0000-000000000000")).thenReturn(PublisherWorkflow.PRIMARY_LOCKSS); + WebRequest request = new GetMethodWebRequest("http://null/EntitlementCheckServeContent?scope=ed.ac.uk&url=http%3A%2F%2Fpublisher.org%2Ftest_journal%2F" ); + InvocationContext ic = sClient.newInvocation(request); + EntitlementCheckServeContent snsc = (EntitlementCheckServeContent) ic.getServlet(); + + WebResponse resp1 = sClient.getResponse(request); + assertResponseOk(resp1); + assertEquals("BlahCached content", resp1.getText()); + } + + public void testCachedUrlLibraryNotification() throws Exception { + initServletRunner(); + pluginMgr.addAu(makeAu()); + Mockito.when(entitlementRegistryClient.getInstitution("ed.ac.uk")).thenReturn("03bd5fc6-97f0-11e4-b270-8932ea886a12"); + Mockito.when(entitlementRegistryClient.isUserEntitled("0740-2783", "03bd5fc6-97f0-11e4-b270-8932ea886a12", "20140101", "20141231")).thenReturn(true); + Mockito.when(entitlementRegistryClient.getPublisher("0740-2783", "03bd5fc6-97f0-11e4-b270-8932ea886a12", "20140101", "20141231")).thenReturn("33333333-0000-0000-0000-000000000000"); + Mockito.when(entitlementRegistryClient.getPublisherWorkflow("33333333-0000-0000-0000-000000000000")).thenReturn(PublisherWorkflow.LIBRARY_NOTIFICATION); + sClient.setExceptionsThrownOnErrorStatus(false); + WebRequest request = new GetMethodWebRequest("http://null/EntitlementCheckServeContent?scope=ed.ac.uk&url=http%3A%2F%2Fpublisher.org%2Ftest_journal%2F" ); + InvocationContext ic = sClient.newInvocation(request); + EntitlementCheckServeContent snsc = (EntitlementCheckServeContent) ic.getServlet(); + + WebResponse resp1 = sClient.getResponse(request); + assertEquals(403, resp1.getResponseCode()); + assertTrue(resp1.getText().contains("

You are not authorised to access the requested URL on this LOCKSS box. Select link1 to view it at the publisher:

http://publisher.org/test_journal/")); + } + + public void testCachedUrlIndexed() throws Exception { + initServletRunner(); + + Connection conn = dbManager.getConnection(); + Long publisherSeq = dbManager.addPublisher(conn, "Wiley"); + String proprietaryId = null; + Long mdItemTypeSeq = metadataManager.findMetadataItemType(conn, MetadataField.ARTICLE_TYPE_JOURNALARTICLE); + Long auMdSeq = null; + String date = "2014"; + String coverage = null; + long fetchTime = 0; + Long parentSeq = metadataManager.findOrCreateJournal(conn, publisherSeq, "07402783", "07402783", "Air and Space", proprietaryId); + Long mdItemSeq = metadataManager.addMdItem(conn, parentSeq, mdItemTypeSeq, auMdSeq, date, coverage, fetchTime); + metadataManager.addMdItemUrl(conn, mdItemSeq, MetadataManager.ACCESS_URL_FEATURE, "http://publisher.org/test_journal/"); + dbManager.commitOrRollback(conn, Logger.getLogger(DbManager.class)); + + Properties props = new Properties(); + props.setProperty("issn", ""); + props.setProperty("eissn", ""); + props.setProperty("attributes.year", ""); + pluginMgr.addAu(makeAu(props)); + Mockito.when(entitlementRegistryClient.getInstitution("ed.ac.uk")).thenReturn("03bd5fc6-97f0-11e4-b270-8932ea886a12"); + Mockito.when(entitlementRegistryClient.isUserEntitled("07402783", "03bd5fc6-97f0-11e4-b270-8932ea886a12", "2014", "2014")).thenReturn(true); + Mockito.when(entitlementRegistryClient.getPublisher("07402783", "03bd5fc6-97f0-11e4-b270-8932ea886a12", "2014", "2014")).thenReturn("33333333-0000-0000-0000-000000000000"); + Mockito.when(entitlementRegistryClient.getPublisherWorkflow("33333333-0000-0000-0000-000000000000")).thenReturn(PublisherWorkflow.PRIMARY_PUBLISHER); + WebRequest request = new GetMethodWebRequest("http://null/EntitlementCheckServeContent?scope=ed.ac.uk&url=http%3A%2F%2Fpublisher.org%2Ftest_journal%2F" ); + InvocationContext ic = sClient.newInvocation(request); + MockEntitlementCheckServeContent snsc = (MockEntitlementCheckServeContent) ic.getServlet(); + LockssUrlConnection connection = mockConnection(200, "BlahFetched content"); + snsc.expectRequest("http://publisher.org/test_journal/", connection); + + WebResponse resp1 = sClient.getResponse(request); + assertResponseOk(resp1); + assertEquals("BlahFetched content", resp1.getText()); + Mockito.verify(connection).addRequestProperty("X-Forwarded-For", "127.0.0.1"); + Mockito.verify(connection).addRequestProperty("X-Lockss-Institution", "ed.ac.uk"); + } + + public void testUnauthorisedUrl() throws Exception { + initServletRunner(); + pluginMgr.addAu(makeAu()); + Mockito.when(entitlementRegistryClient.getInstitution("ed.ac.uk")).thenReturn("03bd5fc6-97f0-11e4-b270-8932ea886a12"); + Mockito.when(entitlementRegistryClient.isUserEntitled("0740-2783", "03bd5fc6-97f0-11e4-b270-8932ea886a12", "20140101", "20141231")).thenReturn(false); + sClient.setExceptionsThrownOnErrorStatus(false); + WebRequest request = new GetMethodWebRequest("http://null/EntitlementCheckServeContent?scope=ed.ac.uk&url=http%3A%2F%2Fpublisher.org%2Ftest_journal%2F" ); + InvocationContext ic = sClient.newInvocation(request); + EntitlementCheckServeContent snsc = (EntitlementCheckServeContent) ic.getServlet(); + + WebResponse resp1 = sClient.getResponse(request); + System.out.println(resp1.getText()); + assertEquals(403, resp1.getResponseCode()); + assertTrue(resp1.getText().contains("

You are not authorised to access the requested URL on this LOCKSS box. Select link1 to view it at the publisher:

http://publisher.org/test_journal/")); + } + + public void testEntitlementRegistryError() throws Exception { + initServletRunner(); + pluginMgr.addAu(makeAu()); + Mockito.when(entitlementRegistryClient.getInstitution("ed.ac.uk")).thenReturn("03bd5fc6-97f0-11e4-b270-8932ea886a12"); + Mockito.when(entitlementRegistryClient.isUserEntitled("0740-2783", "03bd5fc6-97f0-11e4-b270-8932ea886a12", "20140101", "20141231")).thenThrow(new IOException("Could not contact entitlement registry")); + sClient.setExceptionsThrownOnErrorStatus(false); + WebRequest request = new GetMethodWebRequest("http://null/EntitlementCheckServeContent?scope=ed.ac.uk&url=http%3A%2F%2Fpublisher.org%2Ftest_journal%2F" ); + InvocationContext ic = sClient.newInvocation(request); + EntitlementCheckServeContent snsc = (EntitlementCheckServeContent) ic.getServlet(); + + WebResponse resp1 = sClient.getResponse(request); + assertEquals(503, resp1.getResponseCode()); + assertTrue(resp1.getText().contains("

An error occurred trying to access the requested URL on this LOCKSS box. This may be temporary and you may wish to report this, and try again later. Select link1 to view it at the publisher:

http://publisher.org/test_journal/")); + } + + public void testInvalidArchivalUnit() throws Exception { + initServletRunner(); + Properties props = new Properties(); + props.setProperty("issn", ""); + props.setProperty("eissn", ""); + sClient.setExceptionsThrownOnErrorStatus(false); + pluginMgr.addAu(makeAu(props)); + WebRequest request = new GetMethodWebRequest("http://null/EntitlementCheckServeContent?scope=ed.ac.uk&url=http%3A%2F%2Fpublisher.org%2Ftest_journal%2F" ); + InvocationContext ic = sClient.newInvocation(request); + EntitlementCheckServeContent snsc = (EntitlementCheckServeContent) ic.getServlet(); + + WebResponse resp1 = sClient.getResponse(request); + assertEquals(404, resp1.getResponseCode()); + assertTrue(resp1.getText().contains("

The requested URL is not preserved on this LOCKSS box. Select link1 to view it at the publisher:

http://publisher.org/test_journal/")); + } + + // Test no longer valid due to changes to how we extract bibliographic data + // public void testCachedUrlMultipleAUs() throws Exception { + // initServletRunner(); + // pluginMgr.addAu(makeAu()); + // Properties props = new Properties(); + // props.setProperty("issn", "0000-0000"); + // props.setProperty("eissn", "0000-0000"); + // pluginMgr.addAu(makeAu(props)); + // Mockito.when(entitlementRegistryClient.getInstitution("ed.ac.uk")).thenReturn("03bd5fc6-97f0-11e4-b270-8932ea886a12"); + // Mockito.when(entitlementRegistryClient.isUserEntitled("0740-2783", "03bd5fc6-97f0-11e4-b270-8932ea886a12", "20140101", "20141231")).thenReturn(false); + // Mockito.when(entitlementRegistryClient.isUserEntitled("0000-0000", "03bd5fc6-97f0-11e4-b270-8932ea886a12", "20140101", "20141231")).thenReturn(true); + // Mockito.when(entitlementRegistryClient.getPublisher("0000-0000", "03bd5fc6-97f0-11e4-b270-8932ea886a12", "20140101", "20141231")).thenReturn("33333333-0000-0000-0000-000000000000"); + // Mockito.when(entitlementRegistryClient.getPublisherWorkflow("33333333-0000-0000-0000-00000000000000")).thenReturn(PublisherWorkflow.PRIMARY_PUBLISHER); + // WebRequest request = new GetMethodWebRequest("http://null/EntitlementCheckServeContent?scope=ed.ac.uk&url=http%3A%2F%2Fpublisher.org%2Ftest_journal%2F" ); + // InvocationContext ic = sClient.newInvocation(request); + // MockEntitlementCheckServeContent snsc = (MockEntitlementCheckServeContent) ic.getServlet(); + // LockssUrlConnection connection = mockConnection(200, "BlahFetched content"); + // snsc.expectRequest("http://publisher.org/test_journal/", connection); + + // WebResponse resp1 = sClient.getResponse(request); + // assertResponseOk(resp1); + // assertEquals("BlahFetched content", resp1.getText()); + // Mockito.verify(connection).addRequestProperty("X-Forwarded-For", "127.0.0.1"); + // Mockito.verify(connection).addRequestProperty("X-Lockss-Institution", "ed.ac.uk"); + // } + + private static class MockPluginManager extends PluginManager { + private Map> aus; + private MockLockssDaemon theDaemon; + private MockNodeManager nodeManager; + + public MockPluginManager(MockLockssDaemon theDaemon) { + this.aus = new HashMap>(); + this.theDaemon = theDaemon; + this.nodeManager = new MockNodeManager(); + } + + public void addAu(ArchivalUnit au) { + aus.put(au, new ArrayList(au.getStartUrls())); + theDaemon.setNodeManager(nodeManager, au); + } + + public void addAu(ArchivalUnit au, List urls) { + aus.put(au, urls); + theDaemon.setNodeManager(nodeManager, au); + } + + @Override + public List getAllAus() { + return new ArrayList(aus.keySet()); + } + + @Override + public List findCachedUrls(String url, CuContentReq req) { + return this.findCachedUrls(url); + } + + @Override + public List findCachedUrls(String url) { + List cached = new ArrayList(); + for(ArchivalUnit au : aus.keySet()) { + List urls = aus.get(au); + if(urls != null && urls.contains(url)) { + MockCachedUrl cu = new MockCachedUrl(url, au); + cu.addProperty(CachedUrl.PROPERTY_CONTENT_TYPE, "text/html"); + cu.setContent("BlahCached content"); + cached.add(cu); + } + } + return cached; + } + } + + public static class RedirectServlet extends HttpServlet { + public void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + resp.getWriter().print("BlahRedirected content"); + } + } + + private LockssUrlConnection mockConnection(int responseCode, String response) throws IOException { + LockssUrlConnection connection = Mockito.mock(LockssUrlConnection.class); + Mockito.when(connection.getResponseCode()).thenReturn(responseCode); + Mockito.when(connection.getResponseInputStream()).thenReturn(new StringInputStream(response)); + return connection; + } + + public static class MockEntitlementCheckServeContent extends EntitlementCheckServeContent { + private EntitlementCheckServeContent delegate = Mockito.mock(EntitlementCheckServeContent.class); + + public void expectRequest(String url, LockssUrlConnection connection) throws IOException { + Mockito.when(delegate.doOpenConnection(Mockito.eq(url), Mockito.any(LockssUrlConnectionPool.class))).thenReturn(connection); + } + + protected LockssUrlConnection doOpenConnection(String url, LockssUrlConnectionPool pool) throws IOException { + return delegate.doOpenConnection(url, pool); + } + } +} diff --git a/test/src/org/lockss/test/MockLockssDaemon.java b/test/src/org/lockss/test/MockLockssDaemon.java index 9eee37ed2c1..5888463c80e 100644 --- a/test/src/org/lockss/test/MockLockssDaemon.java +++ b/test/src/org/lockss/test/MockLockssDaemon.java @@ -63,7 +63,7 @@ of this software and associated documentation files (the "Software"), to deal import org.lockss.subscription.SubscriptionManager; import org.lockss.util.*; import org.lockss.clockss.*; -import org.lockss.safenet.*; +import org.lockss.entitlement.*; public class MockLockssDaemon extends LockssDaemon { private static Logger log = Logger.getLogger("MockLockssDaemon"); @@ -863,7 +863,16 @@ public void setCron(Cron cron) { */ public void setEntitlementRegistryClient(EntitlementRegistryClient entitlementRegistryClient) { this.entitlementRegistryClient = entitlementRegistryClient; - managerMap.put(LockssDaemon.SAFENET_MANAGER, entitlementRegistryClient); + managerMap.put(LockssDaemon.ENTITLEMENT_REGISTRY_CLIENT, entitlementRegistryClient); + } + + /** + * Set the EntitlementRegistryClient + * @param pluginMan the new manager + */ + public void setCachedEntitlementRegistryClient(EntitlementRegistryClient entitlementRegistryClient) { + this.entitlementRegistryClient = entitlementRegistryClient; + managerMap.put(LockssDaemon.CACHED_ENTITLEMENT_REGISTRY_CLIENT, entitlementRegistryClient); } // AU managers