Make DeveloperSettings a singleton, and other improvements.

By making it a singleton, we do need an explicit Setup operation. However, it means that we can refer to the settings in client code that doesn’t have access to a request or to the ServletContext.

Other refactorings to simplify the logic or make it more scalable.
This commit is contained in:
j2blake 2014-01-04 14:22:29 -05:00
parent f6bd5804d5
commit 245763e9e7
20 changed files with 387 additions and 421 deletions

View file

@ -77,7 +77,7 @@ public class BaseSiteAdminController extends FreemarkerHttpServlet {
}
if (PolicyHelper.isAuthorizedForActions(vreq, SimplePermission.ENABLE_DEVELOPER_PANEL.ACTIONS)) {
urls.put("activateDeveloperPanel", "javascript:new DeveloperPanel(developerAjaxUrl).setupDeveloperPanel({developerEnabled: true});");
urls.put("activateDeveloperPanel", "javascript:new DeveloperPanel(developerAjaxUrl).setupDeveloperPanel({developer_enabled: true});");
}
return urls;

View file

@ -26,7 +26,7 @@ import edu.cornell.mannlib.vitro.webapp.freemarker.loader.FreemarkerTemplateLoad
import edu.cornell.mannlib.vitro.webapp.i18n.freemarker.I18nMethodModel;
import edu.cornell.mannlib.vitro.webapp.startup.StartupStatus;
import edu.cornell.mannlib.vitro.webapp.utils.developer.DeveloperSettings;
import edu.cornell.mannlib.vitro.webapp.utils.developer.DeveloperSettings.Keys;
import edu.cornell.mannlib.vitro.webapp.utils.developer.Key;
import edu.cornell.mannlib.vitro.webapp.web.directives.IndividualShortViewDirective;
import edu.cornell.mannlib.vitro.webapp.web.directives.UrlDirective;
import edu.cornell.mannlib.vitro.webapp.web.directives.WidgetDirective;
@ -70,7 +70,7 @@ public abstract class FreemarkerConfiguration {
confirmInstanceIsSet();
synchronized (instance) {
clearTemplateCacheIfRequested(req);
clearTemplateCacheIfRequested();
keepTemplateLoaderCurrentWithThemeDirectory(req);
setThreadLocalsForRequest(req);
return instance;
@ -84,21 +84,14 @@ public abstract class FreemarkerConfiguration {
}
}
private static void clearTemplateCacheIfRequested(HttpServletRequest req) {
if (isTemplateCacheInvalid(req)) {
/** If the developer doesn't want the cache, clear it every time. */
private static void clearTemplateCacheIfRequested() {
DeveloperSettings settings = DeveloperSettings.getInstance();
if (settings.getBoolean(Key.DEFEAT_FREEMARKER_CACHE)) {
instance.clearTemplateCache();
}
}
/** If the developer doesn't want the cache, it's invalid. */
private static boolean isTemplateCacheInvalid(HttpServletRequest req) {
DeveloperSettings settings = DeveloperSettings.getBean(req);
if (settings.getBoolean(Keys.DEFEAT_FREEMARKER_CACHE)) {
return true;
}
return false;
}
/**
* Keep track of the theme directory. If it changes, create an appropriate
* new TemplateLoader.
@ -110,7 +103,7 @@ public abstract class FreemarkerConfiguration {
HttpServletRequest req) {
String themeDir = getThemeDirectory(req);
if (hasThemeDirectoryChanged(themeDir)
|| haveDeveloperSettingsChanged(req)) {
|| haveDeveloperSettingsChanged()) {
TemplateLoader tl = createTemplateLoader(req, themeDir);
instance.setTemplateLoader(tl);
}
@ -131,9 +124,9 @@ public abstract class FreemarkerConfiguration {
}
}
private static boolean haveDeveloperSettingsChanged(HttpServletRequest req) {
Map<String, Object> settingsMap = DeveloperSettings.getBean(req)
.getSettingsMap();
private static boolean haveDeveloperSettingsChanged() {
Map<String, Object> settingsMap = DeveloperSettings.getInstance()
.getRawSettingsMap();
if (settingsMap.equals(previousSettingsMap)) {
return false;
} else {
@ -167,8 +160,8 @@ public abstract class FreemarkerConfiguration {
TemplateLoader tl = new MultiTemplateLoader(loaderArray);
// If requested, add delimiters to the templates.
DeveloperSettings settings = DeveloperSettings.getBean(req);
if (settings.getBoolean(Keys.INSERT_FREEMARKER_DELIMITERS)) {
DeveloperSettings settings = DeveloperSettings.getInstance();
if (settings.getBoolean(Key.INSERT_FREEMARKER_DELIMITERS)) {
tl = new DelimitingTemplateLoader(tl);
}

View file

@ -22,7 +22,7 @@ import org.apache.commons.logging.LogFactory;
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
import edu.cornell.mannlib.vitro.webapp.i18n.selection.SelectedLocale;
import edu.cornell.mannlib.vitro.webapp.utils.developer.DeveloperSettings;
import edu.cornell.mannlib.vitro.webapp.utils.developer.DeveloperSettings.Keys;
import edu.cornell.mannlib.vitro.webapp.utils.developer.Key;
/**
* Provides access to a bundle of text strings, based on the name of the bundle,
@ -108,7 +108,7 @@ public class I18n {
protected I18nBundle getBundle(String bundleName, HttpServletRequest req) {
log.debug("Getting bundle '" + bundleName + "'");
I18nLogger i18nLogger = new I18nLogger(req);
I18nLogger i18nLogger = new I18nLogger();
try {
checkDevelopmentMode(req);
checkForChangeInThemeDirectory(req);
@ -133,7 +133,7 @@ public class I18n {
* If we are in development mode, clear the cache on each request.
*/
private void checkDevelopmentMode(HttpServletRequest req) {
if (DeveloperSettings.getBean(req).getBoolean(Keys.I18N_DEFEAT_CACHE)) {
if (DeveloperSettings.getInstance().getBoolean(Key.I18N_DEFEAT_CACHE)) {
log.debug("In development mode - clearing the cache.");
clearCacheOnRequest(req);
}

View file

@ -4,13 +4,11 @@ package edu.cornell.mannlib.vitro.webapp.i18n;
import java.util.Arrays;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import edu.cornell.mannlib.vitro.webapp.utils.developer.DeveloperSettings;
import edu.cornell.mannlib.vitro.webapp.utils.developer.DeveloperSettings.Keys;
import edu.cornell.mannlib.vitro.webapp.utils.developer.Key;
/**
* If enabled in developer mode, write a message to the log each time someone
@ -23,10 +21,9 @@ public class I18nLogger {
private final boolean isLogging;
public I18nLogger(HttpServletRequest req) {
DeveloperSettings settings = DeveloperSettings.getBean(req);
this.isLogging = settings.getBoolean(Keys.ENABLED)
&& settings.getBoolean(Keys.I18N_LOG_STRINGS)
public I18nLogger() {
DeveloperSettings settings = DeveloperSettings.getInstance();
this.isLogging = settings.getBoolean(Key.I18N_LOG_STRINGS)
&& log.isInfoEnabled();
}

View file

@ -42,7 +42,7 @@ public class RDFServiceUtils {
* Every factory is wrapped in a logger, so we can dynamically
* enable or disable logging.
*/
return new LoggingRDFServiceFactory(context, factory);
return new LoggingRDFServiceFactory(factory);
} else {
log.error("Expecting an RDFServiceFactory on the context, but found " + o);
return null;

View file

@ -5,8 +5,6 @@ package edu.cornell.mannlib.vitro.webapp.rdfservice.impl.logging;
import java.io.InputStream;
import java.util.List;
import javax.servlet.ServletContext;
import edu.cornell.mannlib.vitro.webapp.rdfservice.ChangeListener;
import edu.cornell.mannlib.vitro.webapp.rdfservice.ChangeSet;
import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService;
@ -19,11 +17,9 @@ import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFServiceException;
* For the other methods, it just delegates to the inner RDFService.
*/
public class LoggingRDFService implements RDFService {
private final ServletContext ctx;
private final RDFService innerService;
LoggingRDFService(ServletContext ctx, RDFService innerService) {
this.ctx = ctx;
LoggingRDFService(RDFService innerService) {
this.innerService = innerService;
}
@ -34,7 +30,7 @@ public class LoggingRDFService implements RDFService {
@Override
public boolean changeSetUpdate(ChangeSet changeSet)
throws RDFServiceException {
try (RDFServiceLogger l = new RDFServiceLogger(ctx, changeSet)) {
try (RDFServiceLogger l = new RDFServiceLogger(changeSet)) {
return innerService.changeSetUpdate(changeSet);
}
}
@ -42,7 +38,7 @@ public class LoggingRDFService implements RDFService {
@Override
public InputStream sparqlConstructQuery(String query,
ModelSerializationFormat resultFormat) throws RDFServiceException {
try (RDFServiceLogger l = new RDFServiceLogger(ctx, resultFormat, query)) {
try (RDFServiceLogger l = new RDFServiceLogger(resultFormat, query)) {
return innerService.sparqlConstructQuery(query, resultFormat);
}
}
@ -50,7 +46,7 @@ public class LoggingRDFService implements RDFService {
@Override
public InputStream sparqlDescribeQuery(String query,
ModelSerializationFormat resultFormat) throws RDFServiceException {
try (RDFServiceLogger l = new RDFServiceLogger(ctx, resultFormat, query)) {
try (RDFServiceLogger l = new RDFServiceLogger(resultFormat, query)) {
return innerService.sparqlDescribeQuery(query, resultFormat);
}
}
@ -58,14 +54,14 @@ public class LoggingRDFService implements RDFService {
@Override
public InputStream sparqlSelectQuery(String query, ResultFormat resultFormat)
throws RDFServiceException {
try (RDFServiceLogger l = new RDFServiceLogger(ctx, resultFormat, query)) {
try (RDFServiceLogger l = new RDFServiceLogger(resultFormat, query)) {
return innerService.sparqlSelectQuery(query, resultFormat);
}
}
@Override
public boolean sparqlAskQuery(String query) throws RDFServiceException {
try (RDFServiceLogger l = new RDFServiceLogger(ctx, query)) {
try (RDFServiceLogger l = new RDFServiceLogger(query)) {
return innerService.sparqlAskQuery(query);
}
}

View file

@ -2,8 +2,6 @@
package edu.cornell.mannlib.vitro.webapp.rdfservice.impl.logging;
import javax.servlet.ServletContext;
import edu.cornell.mannlib.vitro.webapp.rdfservice.ChangeListener;
import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService;
import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFServiceException;
@ -14,23 +12,20 @@ import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFServiceFactory;
* wrapped in a LoggingRDFService.
*/
public class LoggingRDFServiceFactory implements RDFServiceFactory {
private final ServletContext ctx;
private final RDFServiceFactory factory;
public LoggingRDFServiceFactory(ServletContext ctx,
RDFServiceFactory factory) {
this.ctx = ctx;
public LoggingRDFServiceFactory(RDFServiceFactory factory) {
this.factory = factory;
}
@Override
public RDFService getRDFService() {
return new LoggingRDFService(ctx, factory.getRDFService());
return new LoggingRDFService(factory.getRDFService());
}
@Override
public RDFService getShortTermRDFService() {
return new LoggingRDFService(ctx, factory.getShortTermRDFService());
return new LoggingRDFService(factory.getShortTermRDFService());
}
@Override

View file

@ -9,14 +9,12 @@ import java.util.List;
import java.util.ListIterator;
import java.util.regex.Pattern;
import javax.servlet.ServletContext;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import edu.cornell.mannlib.vitro.webapp.utils.developer.DeveloperSettings;
import edu.cornell.mannlib.vitro.webapp.utils.developer.DeveloperSettings.Keys;
import edu.cornell.mannlib.vitro.webapp.utils.developer.Key;
/**
* Writes the log message for the LoggingRDFService.
@ -45,7 +43,6 @@ import edu.cornell.mannlib.vitro.webapp.utils.developer.DeveloperSettings.Keys;
public class RDFServiceLogger implements AutoCloseable {
private static final Log log = LogFactory.getLog(RDFServiceLogger.class);
private final ServletContext ctx;
private final Object[] args;
private boolean isEnabled;
@ -58,31 +55,33 @@ public class RDFServiceLogger implements AutoCloseable {
private long startTime;
public RDFServiceLogger(ServletContext ctx, Object... args) {
this.ctx = ctx;
public RDFServiceLogger(Object... args) {
this.args = args;
try {
getProperties();
if (isEnabled && log.isInfoEnabled()) {
loadStackTrace();
if (passesQueryRestriction() && passesStackRestriction()) {
this.startTime = System.currentTimeMillis();
}
}
} catch (Exception e) {
log.error("Failed to create instance", e);
}
}
private void getProperties() {
DeveloperSettings settings = DeveloperSettings.getBean(ctx);
isEnabled = settings.getBoolean(Keys.LOGGING_RDF_ENABLE);
traceRequested = settings.getBoolean(Keys.LOGGING_RDF_STACK_TRACE);
DeveloperSettings settings = DeveloperSettings.getInstance();
isEnabled = settings.getBoolean(Key.LOGGING_RDF_ENABLE);
traceRequested = settings.getBoolean(Key.LOGGING_RDF_STACK_TRACE);
queryStringRestriction = patternFromSettings(settings,
Keys.LOGGING_RDF_QUERY_RESTRICTION);
Key.LOGGING_RDF_QUERY_RESTRICTION);
callStackRestriction = patternFromSettings(settings,
Keys.LOGGING_RDF_STACK_RESTRICTION);
Key.LOGGING_RDF_STACK_RESTRICTION);
}
private Pattern patternFromSettings(DeveloperSettings settings, Keys key) {
private Pattern patternFromSettings(DeveloperSettings settings, Key key) {
String patternString = settings.getString(key);
if (StringUtils.isBlank(patternString)) {
return null;
@ -160,13 +159,13 @@ public class RDFServiceLogger implements AutoCloseable {
}
private String assembleQueryString() {
StringBuilder query = new StringBuilder();
List<String> stringArgs = new ArrayList<>();
for (Object arg : args) {
if (arg instanceof String) {
query.append((String) arg).append(" ");
stringArgs.add((String) arg);
}
}
return query.deleteCharAt(query.length() - 1).toString();
return StringUtils.join(stringArgs, " ");
}
private boolean passesStackRestriction() {
@ -188,6 +187,7 @@ public class RDFServiceLogger implements AutoCloseable {
@Override
public void close() {
try {
if (startTime != 0L) {
long endTime = System.currentTimeMillis();
@ -199,6 +199,9 @@ public class RDFServiceLogger implements AutoCloseable {
log.info(String.format("%8.3f %s %s %s", elapsedSeconds,
methodName, cleanArgs, formattedTrace));
}
} catch (Exception e) {
log.error("Failed to write log record", e);
}
}
private String formatStackTrace() {

View file

@ -6,10 +6,9 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import edu.cornell.mannlib.vitro.webapp.beans.Individual;
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
import edu.cornell.mannlib.vitro.webapp.services.shortview.FakeApplicationOntologyService.TemplateAndDataGetters;
import edu.cornell.mannlib.vitro.webapp.utils.developer.DeveloperSettings;
import edu.cornell.mannlib.vitro.webapp.utils.developer.DeveloperSettings.Keys;
import edu.cornell.mannlib.vitro.webapp.utils.developer.Key;
/**
* When we use a short view other than the default, log it.
@ -17,31 +16,26 @@ import edu.cornell.mannlib.vitro.webapp.utils.developer.DeveloperSettings.Keys;
public class ShortViewLogger {
private static final Log log = LogFactory.getLog(ShortViewLogger.class);
public static void log(VitroRequest vreq, String contextName,
Individual individual, String classUri, TemplateAndDataGetters tdg) {
if (isLogging(vreq)) {
public static void log(String contextName, Individual individual,
String classUri, TemplateAndDataGetters tdg) {
if (isLogging()) {
log.info("Using custom short view in " + contextName + " because '"
+ individual.getURI() + "' (" + individual.getLabel()
+ ") has type '" + classUri + "': " + tdg);
}
}
public static void log(VitroRequest vreq, String contextName,
Individual individual) {
if (isLogging(vreq)) {
public static void log(String contextName, Individual individual) {
if (isLogging()) {
log.info("Using default short view in " + contextName + " for '"
+ individual.getURI() + "' (" + individual.getLabel() + ")");
}
}
private static boolean isLogging(VitroRequest vreq) {
if (!log.isInfoEnabled()) {
return false;
}
DeveloperSettings settings = DeveloperSettings.getBean(vreq);
return settings.getBoolean(Keys.ENABLED)
&& settings
.getBoolean(Keys.PAGE_CONTENTS_LOG_CUSTOM_SHORT_VIEW);
private static boolean isLogging() {
return log.isInfoEnabled()
&& DeveloperSettings.getInstance().getBoolean(
Key.PAGE_CONTENTS_LOG_CUSTOM_SHORT_VIEW);
}
}

View file

@ -114,13 +114,13 @@ public class ShortViewServiceImpl implements ShortViewService {
TemplateAndDataGetters tdg = faker.getShortViewProperties(vreq,
individual, classUri, svContext.name());
if (tdg != null) {
ShortViewLogger.log(vreq, svContext.name(), individual, classUri, tdg);
ShortViewLogger.log(svContext.name(), individual, classUri, tdg);
return tdg;
}
}
// Didn't find one? Use the default values.
ShortViewLogger.log(vreq, svContext.name(), individual);
ShortViewLogger.log(svContext.name(), individual);
return new TemplateAndDataGetters(svContext.getDefaultTemplateName());
}

View file

@ -3,6 +3,7 @@
package edu.cornell.mannlib.vitro.webapp.utils.developer;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.nio.file.Paths;
import java.util.Arrays;
@ -13,251 +14,92 @@ import java.util.Map;
import java.util.Properties;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import edu.cornell.mannlib.vitro.webapp.config.ConfigurationProperties;
import edu.cornell.mannlib.vitro.webapp.startup.StartupStatus;
/**
* Hold the global developer settings. Render to JSON when requested.
* A singleton holder for the developer settings.
*
* On first request, the "developer.properties" file is loaded from the Vitro
* home directory. If the file doesn't exist, or doesn't contain values for
* certain properties, those propertiew will keep their default values.
* Start with an empty settings map.
*
* The Setup class will read "developer.properties" from the Vitro home
* directory, and load its settings. If the file doesn't exist, or doesn't
* contain values for certain properties, those propertiew will keep their
* default values.
*
* An AJAX request can be used to update the properties. If the request has
* multiple values for a property, the first value will be used. If the request
* does not contain a value for a property, that property will keep its current
* value.
*
* The property names in "developer.properties" are not suitable as fields in
* the HTML panel, because they contain periods. For the HTML panel, we
* translate those periods to underscores.
*
* If the ENABLED flag is not set, then getBinary() will return false for all
* keys, and getString() will return the empty string. This simplifies the logic
* in the client code. Use getRawSettingsMap() to display the actual values in
* the developer panel.
*/
public class DeveloperSettings {
private static final Log log = LogFactory.getLog(DeveloperSettings.class);
public enum Keys {
/**
* Developer mode and developer panel is enabled.
*/
ENABLED("developer.enabled", true),
/**
* Users don't need authority to use the developer panel. But they still
* can't enable it without authority.
*/
PERMIT_ANONYMOUS_CONTROL("developer.permitAnonymousControl", true),
/**
* Load Freemarker templates every time they are requested.
*/
DEFEAT_FREEMARKER_CACHE("developer.defeatFreemarkerCache", true),
/**
* Show where each Freemarker template starts and stops.
*/
INSERT_FREEMARKER_DELIMITERS("developer.insertFreemarkerDelimiters",
true),
/**
* Load language property files every time they are requested.
*/
I18N_DEFEAT_CACHE("developer.i18n.defeatCache", true),
/**
* Enable the I18nLogger to log each string request.
*/
I18N_LOG_STRINGS("developer.i18n.logStringRequests", true),
/**
* Enable the LoggingRDFService
*/
LOGGING_RDF_ENABLE("developer.loggingRDFService.enable", true),
/**
* When logging with the LoggingRDFService, include a stack trace
*/
LOGGING_RDF_STACK_TRACE("developer.loggingRDFService.stackTrace", true),
/**
* Don't log with the LoggingRDFService unless the calling stack meets
* this restriction.
*/
LOGGING_RDF_QUERY_RESTRICTION(
"developer.loggingRDFService.queryRestriction", false),
/**
* Don't log with the LoggingRDFService unless the calling stack meets
* this restriction.
*/
LOGGING_RDF_STACK_RESTRICTION(
"developer.loggingRDFService.stackRestriction", false),
/**
* Tell the CustomListViewLogger to note the use of non-default custom
* list views.
*/
PAGE_CONTENTS_LOG_CUSTOM_LIST_VIEW(
"developer.pageContents.logCustomListView", true),
/**
* Tell the ShortViewLogger to note the use of non-default short views.
*/
PAGE_CONTENTS_LOG_CUSTOM_SHORT_VIEW(
"developer.pageContents.logCustomShortView", true);
private final String propertyName;
private final String elementId;
private final boolean bool;
private Keys(String propertyName, boolean bool) {
this.propertyName = propertyName;
this.elementId = produceElementId();
this.bool = bool;
}
public String propertyName() {
return propertyName;
}
public String elementId() {
return elementId;
}
boolean isBoolean() {
return bool;
}
/**
* The element ID is camel-case instead of period-delimited. So
* "developer.enabled" becomes "developerEnabled".
*/
String produceElementId() {
StringBuilder id = new StringBuilder(propertyName.length());
boolean capitalize = false;
for (int i = 0; i < propertyName.length(); i++) {
char c = propertyName.charAt(i);
if (c == '.') {
capitalize = true;
} else if (capitalize) {
id.append(Character.toUpperCase(c));
capitalize = false;
} else {
id.append(c);
}
}
return id.toString();
}
@Override
public String toString() {
return propertyName;
}
static Keys fromElementId(String id) {
for (Keys k : Keys.values()) {
if (k.elementId.equals(id)) {
return k;
}
}
log.error("Can't find key for element id: '" + id + "'");
return null;
}
static Keys fromPropertyName(String name) {
for (Keys k : Keys.values()) {
if (k.propertyName.equals(name)) {
return k;
}
}
log.error("Can't find key for property name: '" + name + "'");
return null;
}
}
// ----------------------------------------------------------------------
// The factory
// ----------------------------------------------------------------------
protected static final String ATTRIBUTE_NAME = DeveloperSettings.class
.getName();
private static final DeveloperSettings instance = new DeveloperSettings();
public static DeveloperSettings getBean(HttpServletRequest req) {
return getBean(req.getSession().getServletContext());
}
public static DeveloperSettings getBean(ServletContext ctx) {
Object o = ctx.getAttribute(ATTRIBUTE_NAME);
if (o instanceof DeveloperSettings) {
return (DeveloperSettings) o;
} else {
DeveloperSettings ds = new DeveloperSettings(ctx);
ctx.setAttribute(ATTRIBUTE_NAME, ds);
return ds;
}
public static DeveloperSettings getInstance() {
return instance;
}
// ----------------------------------------------------------------------
// The instance
// ----------------------------------------------------------------------
private final Map<Keys, Object> settings = new EnumMap<>(Keys.class);
private final Map<Key, String> settings;
protected DeveloperSettings(ServletContext ctx) {
updateFromFile(ctx);
private DeveloperSettings() {
this.settings = new EnumMap<>(Key.class);
}
/**
* Read the initial settings from "developer.properties" in the Vitro home
* directory.
*
* This method is "protected" so we can override it for unit tests.
*/
protected void updateFromFile(ServletContext ctx) {
Map<Keys, String> fromFile = new HashMap<>();
ConfigurationProperties props = ConfigurationProperties.getBean(ctx);
String home = props.getProperty("vitro.home");
File dsFile = Paths.get(home, "developer.properties").toFile();
if (dsFile.isFile()) {
try (FileReader reader = new FileReader(dsFile)) {
Properties dsProps = new Properties();
dsProps.load(reader);
for (String key : dsProps.stringPropertyNames()) {
fromFile.put(Keys.fromPropertyName(key),
dsProps.getProperty(key));
}
} catch (Exception e) {
log.warn("Failed to load 'developer.properties' file.", e);
}
} else {
log.debug("No developer.properties file.");
}
log.debug("Properties from file: " + fromFile);
update(fromFile);
}
/** Provide the parameter map from the HttpServletRequest */
public void updateFromRequest(Map<String, String[]> parameterMap) {
if (log.isDebugEnabled()) {
dumpParameterMap(parameterMap);
}
Map<Keys, String> fromRequest = new HashMap<>();
Map<Key, String> fromRequest = new HashMap<>();
for (String key : parameterMap.keySet()) {
fromRequest.put(Keys.fromElementId(key), parameterMap.get(key)[0]);
fromRequest.put(Key.fromElementId(key), parameterMap.get(key)[0]);
}
update(fromRequest);
}
private void update(Map<Keys, String> changedSettings) {
for (Keys key : Keys.values()) {
public void updateFromProperties(Properties properties) {
Map<Key, String> fromFile = new HashMap<>();
for (String key : properties.stringPropertyNames()) {
fromFile.put(Key.fromPropertyName(key), properties.getProperty(key));
}
update(fromFile);
}
/**
* Update by known keys, so we will ignore any irrelevant request
* parameters, or incorrect properties.
*/
private void update(Map<Key, String> changedSettings) {
for (Key key : Key.values()) {
String s = changedSettings.get(key);
if (s != null) {
if (key.isBoolean()) {
settings.put(key, Boolean.valueOf(s));
settings.put(key, Boolean.valueOf(s).toString());
} else {
settings.put(key, s);
}
@ -266,48 +108,68 @@ public class DeveloperSettings {
log.debug("DeveloperSettings: " + this);
}
public Object get(Keys key) {
if (key.isBoolean()) {
return getBoolean(key);
} else {
return getString(key);
}
}
public boolean getBoolean(Keys key) {
/**
* If developerMode is enabled, return the boolean value of the stored
* setting.
*/
public boolean getBoolean(Key key) {
if (!key.isBoolean()) {
throw new IllegalArgumentException("Key '" + key
+ "' does not take a boolean value.");
}
if (settings.containsKey(key)) {
if (Boolean.TRUE.equals(settings.get(Keys.ENABLED))) {
return (Boolean) settings.get(key);
}
log.warn("Key '" + key + "' does not take a boolean value.");
}
if (isDeveloperModeEnabled()) {
return Boolean.valueOf(settings.get(key));
} else {
return false;
}
}
public String getString(Keys key) {
/**
* If developerMode is enabled and the setting has a value, return that
* value. Otherwise, return the empty string.
*/
public String getString(Key key) {
if (key.isBoolean()) {
throw new IllegalArgumentException("Key '" + key
+ "' takes a boolean value.");
}
if (settings.containsKey(key)) {
if (Boolean.TRUE.equals(settings.get(Keys.ENABLED))) {
return (String) settings.get(key);
}
log.warn("Key '" + key + "' takes a boolean value.");
}
String value = settings.get(key);
if (value != null && isDeveloperModeEnabled()) {
return value;
} else {
return "";
}
}
public Map<String, Object> getSettingsMap() {
private boolean isDeveloperModeEnabled() {
return Boolean.valueOf(settings.get(Key.ENABLED));
}
/**
* Get the values of all the settings, by element ID, regardless of whether
* developerMode is enabled or not. Boolean settings are represented as
* actual Booleans, so Freemarker can perform logical tests on them.
*/
public Map<String, Object> getRawSettingsMap() {
Map<String, Object> map = new HashMap<>();
for (Keys key : Keys.values()) {
map.put(key.elementId(), get(key));
for (Key key : Key.values()) {
map.put(key.elementId(), getRawValue(key));
}
return map;
}
/**
* Get a String or Boolean value, as appropriate for the key. A boolean key
* with no value returns false. A non-boolean key with no value returns the
* empty string.
*/
private Object getRawValue(Key key) {
String value = settings.get(key);
if (key.isBoolean()) {
return Boolean.valueOf(value);
} else {
return (value == null) ? "" : value;
}
}
@Override
public String toString() {
return "DeveloperSettings" + settings;
@ -322,4 +184,42 @@ public class DeveloperSettings {
log.debug("Parameter map: " + map);
}
// ----------------------------------------------------------------------
// Setup class
// ----------------------------------------------------------------------
public static class Setup implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
ServletContext ctx = sce.getServletContext();
StartupStatus ss = StartupStatus.getBean(ctx);
ConfigurationProperties props = ConfigurationProperties
.getBean(ctx);
DeveloperSettings devSettings = DeveloperSettings.getInstance();
String home = props.getProperty("vitro.home");
File dsFile = Paths.get(home, "developer.properties").toFile();
try (FileReader reader = new FileReader(dsFile)) {
Properties dsProps = new Properties();
dsProps.load(reader);
devSettings.updateFromProperties(dsProps);
ss.info(this, "Loaded the 'developer.properties' file: "
+ devSettings);
} catch (FileNotFoundException e) {
ss.info(this, "'developer.properties' file does not exist.");
} catch (Exception e) {
ss.warning(this,
"Failed to load the 'developer.properties' file.", e);
}
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
// Nothing to remove.
}
}
}

View file

@ -2,7 +2,7 @@
package edu.cornell.mannlib.vitro.webapp.utils.developer;
import static edu.cornell.mannlib.vitro.webapp.utils.developer.DeveloperSettings.Keys.PERMIT_ANONYMOUS_CONTROL;
import static edu.cornell.mannlib.vitro.webapp.utils.developer.Key.PERMIT_ANONYMOUS_CONTROL;
import java.io.IOException;
import java.util.HashMap;
@ -42,7 +42,7 @@ public class DeveloperSettingsServlet extends VitroAjaxController {
@Override
protected void doRequest(VitroRequest vreq, HttpServletResponse resp)
throws ServletException, IOException {
DeveloperSettings settings = DeveloperSettings.getBean(vreq);
DeveloperSettings settings = DeveloperSettings.getInstance();
/*
* Are they allowed to control the panel?
@ -70,8 +70,9 @@ public class DeveloperSettingsServlet extends VitroAjaxController {
private Map<String, Object> buildBodyMap(boolean authorized,
DeveloperSettings settings) {
Map<String, Object> settingsMap = new HashMap<>();
settingsMap.putAll(settings.getSettingsMap());
settingsMap.putAll(settings.getRawSettingsMap());
settingsMap.put("mayControl", authorized);
Map<String, Object> bodyMap = new HashMap<>();
bodyMap.put("settings", settingsMap);
return bodyMap;
@ -84,7 +85,7 @@ public class DeveloperSettingsServlet extends VitroAjaxController {
}
private boolean isAuthorized(VitroRequest vreq) {
boolean authBySetting = DeveloperSettings.getBean(vreq).getBoolean(
boolean authBySetting = DeveloperSettings.getInstance().getBoolean(
PERMIT_ANONYMOUS_CONTROL);
boolean authByPolicy = PolicyHelper.isAuthorizedForActions(vreq,
SimplePermission.ENABLE_DEVELOPER_PANEL.ACTION);

View file

@ -0,0 +1,129 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.webapp.utils.developer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Keys for the DeveloperSettings. Each key holds these values:
*
* A property name, which specifies the key in developer.properties, like
* "this.thatThing"
*
* A flag to say whether this key controls a boolean value. If false, then the
* value is a string.
*
* We can derive the element ID for each key by replacing the periods in the
* property name with underscores.
*/
public enum Key {
/**
* Developer mode and developer panel is enabled.
*/
ENABLED("developer.enabled", true),
/**
* If the developer panel is enabled, can a non-logged-in user change the
* settings?
*/
PERMIT_ANONYMOUS_CONTROL("developer.permitAnonymousControl", true),
/**
* Load Freemarker templates every time they are requested.
*/
DEFEAT_FREEMARKER_CACHE("developer.defeatFreemarkerCache", true),
/**
* Show where each Freemarker template starts and stops.
*/
INSERT_FREEMARKER_DELIMITERS("developer.insertFreemarkerDelimiters", true),
/**
* Load language property files every time they are requested.
*/
I18N_DEFEAT_CACHE("developer.i18n.defeatCache", true),
/**
* Enable the I18nLogger to log each string request.
*/
I18N_LOG_STRINGS("developer.i18n.logStringRequests", true),
/**
* Enable the LoggingRDFService
*/
LOGGING_RDF_ENABLE("developer.loggingRDFService.enable", true),
/**
* When logging with the LoggingRDFService, include a stack trace
*/
LOGGING_RDF_STACK_TRACE("developer.loggingRDFService.stackTrace", true),
/**
* Don't log with the LoggingRDFService unless the calling stack meets this
* restriction.
*/
LOGGING_RDF_QUERY_RESTRICTION(
"developer.loggingRDFService.queryRestriction", false),
/**
* Don't log with the LoggingRDFService unless the calling stack meets this
* restriction.
*/
LOGGING_RDF_STACK_RESTRICTION(
"developer.loggingRDFService.stackRestriction", false),
/**
* Tell the CustomListViewLogger to note the use of non-default custom list
* views.
*/
PAGE_CONTENTS_LOG_CUSTOM_LIST_VIEW(
"developer.pageContents.logCustomListView", true),
/**
* Tell the ShortViewLogger to note the use of non-default short views.
*/
PAGE_CONTENTS_LOG_CUSTOM_SHORT_VIEW(
"developer.pageContents.logCustomShortView", true);
private static final Log log = LogFactory.getLog(Key.class);
private final String propertyName;
private final boolean bool;
private Key(String propertyName, boolean bool) {
this.propertyName = propertyName;
this.bool = bool;
}
public String propertyName() {
return propertyName;
}
public String elementId() {
return propertyName.replace('.', '_');
}
boolean isBoolean() {
return bool;
}
@Override
public String toString() {
return propertyName;
}
static Key fromElementId(String id) {
return fromPropertyName(id.replace('_', '.'));
}
static Key fromPropertyName(String name) {
for (Key k : Key.values()) {
if (k.propertyName.equals(name)) {
return k;
}
}
log.error("Can't find key for property name: '" + name + "'");
return null;
}
}

View file

@ -6,9 +6,8 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import edu.cornell.mannlib.vitro.webapp.beans.ObjectProperty;
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
import edu.cornell.mannlib.vitro.webapp.utils.developer.DeveloperSettings;
import edu.cornell.mannlib.vitro.webapp.utils.developer.DeveloperSettings.Keys;
import edu.cornell.mannlib.vitro.webapp.utils.developer.Key;
/**
* If enabled in the developer settings (and log levels), log every non-default
@ -18,21 +17,19 @@ public class CustomListViewLogger {
private static final Log log = LogFactory
.getLog(CustomListViewLogger.class);
public static void log(VitroRequest vreq, ObjectProperty op,
String configFileName) {
if (isLogging(vreq)) {
public static void log(ObjectProperty op, String configFileName) {
if (isLogging()) {
log.info("Using list view: '" + configFileName + "' for "
+ op.getURI() + " (" + op.getLabel() + ")");
}
}
private static boolean isLogging(VitroRequest vreq) {
private static boolean isLogging() {
if (!log.isInfoEnabled()) {
return false;
}
DeveloperSettings settings = DeveloperSettings.getBean(vreq);
return settings.getBoolean(Keys.ENABLED)
&& settings.getBoolean(Keys.PAGE_CONTENTS_LOG_CUSTOM_LIST_VIEW);
DeveloperSettings settings = DeveloperSettings.getInstance();
return settings.getBoolean(Key.PAGE_CONTENTS_LOG_CUSTOM_LIST_VIEW);
}
}

View file

@ -21,7 +21,6 @@ import edu.cornell.mannlib.vitro.webapp.web.templatemodels.individual.ObjectProp
import edu.cornell.mannlib.vitro.webapp.web.templatemodels.individual.ObjectPropertyTemplateModel;
import edu.cornell.mannlib.vitro.webapp.web.templatemodels.individual.ObjectPropertyTemplateModel.ConfigError;
import freemarker.cache.TemplateLoader;
import freemarker.template.Configuration;
public class PropertyListConfig {
private static final Log log = LogFactory.getLog(PropertyListConfig.class);
@ -62,7 +61,7 @@ public class PropertyListConfig {
if (configFileName == null) { // no custom config; use default config
configFileName = DEFAULT_CONFIG_FILE_NAME;
} else {
CustomListViewLogger.log(vreq, op, configFileName);
CustomListViewLogger.log(op, configFileName);
}
log.debug("Using list view config file " + configFileName + " for object property " + op.getURI());

View file

@ -1,33 +0,0 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package stubs.edu.cornell.mannlib.vitro.webapp.utils.developer;
import javax.servlet.ServletContext;
import edu.cornell.mannlib.vitro.webapp.utils.developer.DeveloperSettings;
/**
* Do everything that a standard DeveloperSettings would do, except loading from
* a properties file.
*
* That way, we don't require ConfigurationProperties to find the Vitro home
* directory, so we don't throw errors if there is no ConfigurationProperties.
*/
public class DeveloperSettingsStub extends DeveloperSettings {
/**
* Factory method. Create the stub and set it into the ServletContext.
*/
public static void set(ServletContext ctx) {
ctx.setAttribute(ATTRIBUTE_NAME, new DeveloperSettingsStub(ctx));
}
protected DeveloperSettingsStub(ServletContext ctx) {
super(ctx);
}
@Override
protected void updateFromFile(ServletContext ctx) {
// Don't bother.
}
}

View file

@ -21,8 +21,6 @@ import javax.servlet.ServletException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import stubs.edu.cornell.mannlib.vitro.webapp.utils.developer.DeveloperSettingsStub;
/**
* A simple stand-in for the {@link ServletContext}, for use in unit tests.
*/
@ -38,11 +36,6 @@ public class ServletContextStub implements ServletContext {
private final Map<String, String> mockResources = new HashMap<String, String>();
private final Map<String, String> realPaths = new HashMap<String, String>();
public ServletContextStub() {
// Assume that unit tests won't want to use Developer mode.
DeveloperSettingsStub.set(this);
}
public void setContextPath(String contextPath) {
if (contextPath == null) {
throw new NullPointerException("contextPath may not be null.");

View file

@ -11,6 +11,8 @@ edu.cornell.mannlib.vitro.webapp.config.ConfigurationPropertiesSetup
edu.cornell.mannlib.vitro.webapp.config.ConfigurationPropertiesSmokeTests
edu.cornell.mannlib.vitro.webapp.utils.developer.DeveloperSettings$Setup
edu.cornell.mannlib.vitro.webapp.config.RevisionInfoSetup
edu.cornell.mannlib.vitro.webapp.email.FreemarkerEmailFactory$Setup

View file

@ -34,41 +34,41 @@ function DeveloperPanel(developerAjaxUrl) {
document.getElementById("developerPanelSaveButton").onclick = function() {
updateDeveloperPanel(collectFormData());
}
document.getElementById("developerEnabled").onchange = updateDisabledFields
document.getElementById("developerLoggingRDFServiceEnable").onchange = updateDisabledFields
document.getElementById("developer_enabled").onchange = updateDisabledFields
document.getElementById("developer_loggingRDFService_enable").onchange = updateDisabledFields
}
function updateDisabledFields() {
var developerEnabled = document.getElementById("developerEnabled").checked;
document.getElementById("developerPermitAnonymousControl").disabled = !developerEnabled;
document.getElementById("developerDefeatFreemarkerCache").disabled = !developerEnabled;
document.getElementById("developerInsertFreemarkerDelimiters").disabled = !developerEnabled;
document.getElementById("developerPageContentsLogCustomListView").disabled = !developerEnabled;
document.getElementById("developerPageContentsLogCustomShortView").disabled = !developerEnabled;
document.getElementById("developerI18nDefeatCache").disabled = !developerEnabled;
document.getElementById("developerI18nLogStringRequests").disabled = !developerEnabled;
document.getElementById("developerLoggingRDFServiceEnable").disabled = !developerEnabled;
var developerEnabled = document.getElementById("developer_enabled").checked;
document.getElementById("developer_permitAnonymousControl").disabled = !developerEnabled;
document.getElementById("developer_defeatFreemarkerCache").disabled = !developerEnabled;
document.getElementById("developer_insertFreemarkerDelimiters").disabled = !developerEnabled;
document.getElementById("developer_pageContents_logCustomListView").disabled = !developerEnabled;
document.getElementById("developer_pageContents_logCustomShortView").disabled = !developerEnabled;
document.getElementById("developer_i18n_defeatCache").disabled = !developerEnabled;
document.getElementById("developer_i18n_logStringRequests").disabled = !developerEnabled;
document.getElementById("developer_loggingRDFService_enable").disabled = !developerEnabled;
var rdfServiceEnabled = developerEnabled && document.getElementById("developerLoggingRDFServiceEnable").checked;
document.getElementById("developerLoggingRDFServiceStackTrace").disabled = !rdfServiceEnabled;
document.getElementById("developerLoggingRDFServiceQueryRestriction").disabled = !rdfServiceEnabled;
document.getElementById("developerLoggingRDFServiceStackRestriction").disabled = !rdfServiceEnabled;
var rdfServiceEnabled = developerEnabled && document.getElementById("developer_loggingRDFService_enable").checked;
document.getElementById("developer_loggingRDFService_stackTrace").disabled = !rdfServiceEnabled;
document.getElementById("developer_loggingRDFService_queryRestriction").disabled = !rdfServiceEnabled;
document.getElementById("developer_loggingRDFService_stackRestriction").disabled = !rdfServiceEnabled;
}
function collectFormData() {
var data = new Object();
getCheckbox("developerEnabled", data);
getCheckbox("developerPermitAnonymousControl", data);
getCheckbox("developerDefeatFreemarkerCache", data);
getCheckbox("developerInsertFreemarkerDelimiters", data);
getCheckbox("developerPageContentsLogCustomListView", data);
getCheckbox("developerPageContentsLogCustomShortView", data);
getCheckbox("developerI18nDefeatCache", data);
getCheckbox("developerI18nLogStringRequests", data);
getCheckbox("developerLoggingRDFServiceEnable", data);
getCheckbox("developerLoggingRDFServiceStackTrace", data);
getText("developerLoggingRDFServiceQueryRestriction", data);
getText("developerLoggingRDFServiceStackRestriction", data);
getCheckbox("developer_enabled", data);
getCheckbox("developer_permitAnonymousControl", data);
getCheckbox("developer_defeatFreemarkerCache", data);
getCheckbox("developer_insertFreemarkerDelimiters", data);
getCheckbox("developer_pageContents_logCustomListView", data);
getCheckbox("developer_pageContents_logCustomShortView", data);
getCheckbox("developer_i18n_defeatCache", data);
getCheckbox("developer_i18n_logStringRequests", data);
getCheckbox("developer_loggingRDFService_enable", data);
getCheckbox("developer_loggingRDFService_stackTrace", data);
getText("developer_loggingRDFService_queryRestriction", data);
getText("developer_loggingRDFService_stackRestriction", data);
return data;
}

View file

@ -9,7 +9,7 @@
<input type="text" id="${key}" size="30" value="${settings[key]}" >
</#macro>
<#if !settings.developerEnabled>
<#if !settings.developer_enabled>
<#elseif !settings.mayControl>
<div class="developer">
<h1>${siteName} is running in developer mode.</h1>
@ -22,11 +22,11 @@
<div id="developerPanelBody">
<div>
<label>
<@showCheckbox "developerEnabled" />
<@showCheckbox "developer_enabled" />
Enable developer mode
</label>
<label>
<@showCheckbox "developerPermitAnonymousControl" />
<@showCheckbox "developer_permitAnonymousControl" />
Allow anonymous user to see and modify developer settings
</label>
</div>
@ -35,11 +35,11 @@
<div class="container">
Page configuration
<label>
<@showCheckbox "developerPageContentsLogCustomListView" />
<@showCheckbox "developer_pageContents_logCustomListView" />
Log the use of custom list view XML files.
</label>
<label>
<@showCheckbox "developerPageContentsLogCustomShortView" />
<@showCheckbox "developer_pageContents_logCustomShortView" />
Log the use of custom short views in search, index and browse pages.
</label>
</div>
@ -47,11 +47,11 @@
<div class="container">
Language support
<label>
<@showCheckbox "developerI18nDefeatCache" />
<@showCheckbox "developer_i18n_defeatCache" />
Defeat the cache of language property files
</label>
<label>
<@showCheckbox "developerI18nLogStringRequests" />
<@showCheckbox "developer_i18n_logStringRequests" />
Log the retrieval of language strings
</label>
</div>
@ -71,11 +71,11 @@
<div class="container">
Freemarker templates
<label>
<@showCheckbox "developerDefeatFreemarkerCache" />
<@showCheckbox "developer_defeatFreemarkerCache" />
Defeat the template cache
</label>
<label>
<@showCheckbox "developerInsertFreemarkerDelimiters" />
<@showCheckbox "developer_insertFreemarkerDelimiters" />
Insert HTML comments at start and end of templates
</label>
</div>
@ -83,21 +83,21 @@
<div class="container">
SPARQL Queries
<label>
<@showCheckbox "developerLoggingRDFServiceEnable" />
<@showCheckbox "developer_loggingRDFService_enable" />
Log each query
</label>
<div class="within">
<label>
<@showCheckbox "developerLoggingRDFServiceStackTrace" />
<@showCheckbox "developer_loggingRDFService_stackTrace" />
Add stack trace
</label>
<label>
Restrict by query string
<@showTextbox "developerLoggingRDFServiceQueryRestriction" />
<@showTextbox "developer_loggingRDFService_queryRestriction" />
</label>
<label>
Restrict by calling stack
<@showTextbox "developerLoggingRDFServiceStackRestriction" />
<@showTextbox "developer_loggingRDFService_stackRestriction" />
</label>
</div>
</div>