VIVO-541 First cut at the developer panel.
This commit is contained in:
parent
35251e89f4
commit
0fce9f6a7b
21 changed files with 762 additions and 139 deletions
|
@ -780,65 +780,6 @@
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td colspan="2">
|
|
||||||
<b>For developers only.</b>
|
|
||||||
Defeat the Freemarker template cache, so each template
|
|
||||||
is read from disk on each request. This permits developers to immediately
|
|
||||||
see the effect of changes to the template. The default is <code>false</code>, which
|
|
||||||
means that a cached copy of each template will be used for 60 seconds
|
|
||||||
before the disk is checked for a new version.
|
|
||||||
<br/><b>Setting this option to "true" slows down Vitro performance.</b>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr class="odd_row">
|
|
||||||
<td>
|
|
||||||
developer.defeatFreemarkerCache
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
false
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td colspan="2">
|
|
||||||
<b>For developers only.</b>
|
|
||||||
Defeat the cache of language-specific text strings,
|
|
||||||
so the language file is read from disk on each request.
|
|
||||||
This permits developers to immediately
|
|
||||||
see the effect of changes to the text strings.
|
|
||||||
The default is <code>false</code>, which means that the language file is
|
|
||||||
read when VIVO starts up, or when a new theme is selected.
|
|
||||||
<br/><b>Setting this option to "true" slows down Vitro performance.</b>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr class="odd_row">
|
|
||||||
<td>
|
|
||||||
developer.defeatI18nCache = true
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
false
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td colspan="2">
|
|
||||||
<b>For developers only.</b>
|
|
||||||
Add starting and ending delimiters to each Freemarker template, so you can see
|
|
||||||
which template were invoked by viewing the generated HTML.
|
|
||||||
The default is <code>false</code>.
|
|
||||||
<br/><b>Setting this option to "true" slows down Vitro performance.</b>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr class="odd_row blue">
|
|
||||||
<td>
|
|
||||||
developer.insertFreemarkerDelimiters = true
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
false
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
|
|
@ -263,7 +263,12 @@
|
||||||
<target name="prepareVitroHomeDir" depends="prepare">
|
<target name="prepareVitroHomeDir" depends="prepare">
|
||||||
<mkdir dir="${vitrohome.build.dir}" />
|
<mkdir dir="${vitrohome.build.dir}" />
|
||||||
<mkdir dir="${vitrohome.image.dir}" />
|
<mkdir dir="${vitrohome.image.dir}" />
|
||||||
<copy todir="${vitrohome.image.dir}" file="${appbase.dir}/config/example.runtime.properties" />
|
<copy todir="${vitrohome.image.dir}" >
|
||||||
|
<fileset dir="${appbase.dir}/config" >
|
||||||
|
<include name="example.runtime.properties" />
|
||||||
|
<include name="example.developer.properties" />
|
||||||
|
</fileset>
|
||||||
|
</copy>
|
||||||
<copy todir="${vitrohome.image.dir}" >
|
<copy todir="${vitrohome.image.dir}" >
|
||||||
<fileset dir="${appbase.dir}" >
|
<fileset dir="${appbase.dir}" >
|
||||||
<include name="rdf/**/*" />
|
<include name="rdf/**/*" />
|
||||||
|
|
107
webapp/config/example.developer.properties
Normal file
107
webapp/config/example.developer.properties
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
#
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Runtime properties for developer mode.
|
||||||
|
#
|
||||||
|
# If the developer.properties file is present in your VIVO home directory, it
|
||||||
|
# will be loaded as VIVO starts up, taking effect immediately.
|
||||||
|
#
|
||||||
|
# Each of these properties can be set or changed while VIVO is running, but it
|
||||||
|
# can be convenient to set them in advance.
|
||||||
|
#
|
||||||
|
# WARNING: Some of these options can seriously degrade performance. They should
|
||||||
|
# not be enabled in a production instance of VIVO.
|
||||||
|
#
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
#
|
||||||
|
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
# General options
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#
|
||||||
|
# The "master switch" for developer mode. If this is not set to true, then none
|
||||||
|
# of the other properties have any effect.
|
||||||
|
#
|
||||||
|
# developer.enabled = true
|
||||||
|
|
||||||
|
#
|
||||||
|
# If developer mode is enabled, this will determine who can modify the
|
||||||
|
# developer settings. If 'true', then any user can modify the settings. If
|
||||||
|
# false, then only a site administrator (or root) can modify the settings.
|
||||||
|
# The default is 'false'.
|
||||||
|
#
|
||||||
|
# developer.permitAnonymousControl
|
||||||
|
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
# Freemarker
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#
|
||||||
|
# Add HTML comments to each Freemarker template, so you can see what each
|
||||||
|
# templates to the page, by viewing the source of the page in the browser.
|
||||||
|
# The default is 'false'.
|
||||||
|
#
|
||||||
|
# developer.insertFreemarkerDelimiters = true
|
||||||
|
|
||||||
|
#
|
||||||
|
# Defeat the Freemarker template cache, so each template is read from disk
|
||||||
|
# on each request. This permits developers to immediately see the effect of
|
||||||
|
# changes to the template. The default is 'false', which means that a cached
|
||||||
|
# copy of each template will be used for 60 seconds before the disk is checked
|
||||||
|
# for a new version.
|
||||||
|
#
|
||||||
|
# developer.defeatFreemarkerCache = true
|
||||||
|
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
# Internationalization
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#
|
||||||
|
# Defeat the cache of language-specific text strings, so the language file
|
||||||
|
# is read from disk on each request. This permits developers to immediately
|
||||||
|
# see the effect of changes to the text strings. The default is 'false', which
|
||||||
|
# means that the language file is only read when VIVO starts up, or when a new
|
||||||
|
# theme is selected.
|
||||||
|
#
|
||||||
|
# developer.i18n.defeatCache = true
|
||||||
|
|
||||||
|
#
|
||||||
|
# Write a line to the log every time a template or a controller requests a
|
||||||
|
# language-specific string from the properties files.
|
||||||
|
#
|
||||||
|
# developer.i18n.logStringRequests
|
||||||
|
|
||||||
|
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
# Logging SPARQL queries
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#
|
||||||
|
# Turn on logging of all SPARQL queries. The logging is at the INFO level.
|
||||||
|
# Each entry includes:
|
||||||
|
# - the elapsed time spent on the query, in seconds,
|
||||||
|
# - the name of the method on RDFService that received the query,
|
||||||
|
# - the format of the result stream from the RDFService method,
|
||||||
|
# - the text of the query.
|
||||||
|
# Note that all access to the content models is done through SPARQL queries,
|
||||||
|
# but some go through translation layers before reaching the RDFService for
|
||||||
|
# logging and execution. The default is 'false'.
|
||||||
|
#
|
||||||
|
# developer.loggingRDFService.enable = true
|
||||||
|
|
||||||
|
#
|
||||||
|
# If SPARQL query logging is enabled, this will add a stack trace to each log
|
||||||
|
# entry. The stack trace is abridged, so it starts after the
|
||||||
|
# ApplicationFilterChain, omits any Jena classes, and ends at the RDFService.
|
||||||
|
# The default is 'false'.
|
||||||
|
#
|
||||||
|
# developer.loggingRDFService.stackTrace = true
|
||||||
|
|
||||||
|
#
|
||||||
|
# If SPARQL query logging is enabled, a regular expression can be used to
|
||||||
|
# restrict the number of entries that are produced. The expression is
|
||||||
|
# tested against each line in the (unabridged) stack trace. If the
|
||||||
|
# expression doesn't match any line in the stack trace, then no log entry
|
||||||
|
# is made. The default is 'false'.
|
||||||
|
#
|
||||||
|
# developer.loggingRDFService.restriction = true
|
|
@ -152,35 +152,3 @@ RDFService.languageFilter = true
|
||||||
# This should not be used with languages.forceLocale, which will override it.
|
# This should not be used with languages.forceLocale, which will override it.
|
||||||
#
|
#
|
||||||
# languages.selectableLocales = en, es, fr
|
# languages.selectableLocales = en, es, fr
|
||||||
|
|
||||||
#
|
|
||||||
# For developers only: Setting this option to "true" slows down Vitro performance.
|
|
||||||
#
|
|
||||||
# Defeat the Freemarker template cache, so each template is read from disk
|
|
||||||
# on each request. This permits developers to immediately see the effect of
|
|
||||||
# changes to the template. The default is <code>false</code>, which means
|
|
||||||
# that a cached copy of each template will be used for 60 seconds before
|
|
||||||
# the disk is checked for a new version.
|
|
||||||
#
|
|
||||||
# developer.defeatFreemarkerCache = true
|
|
||||||
|
|
||||||
#
|
|
||||||
# For developers only: Setting this option to "true" slows down Vitro performance.
|
|
||||||
#
|
|
||||||
# Defeat the cache of language-specific text strings, so the language file
|
|
||||||
# is read from disk on each request. This permits developers to immediately
|
|
||||||
# see the effect of changes to the text strings. The default is
|
|
||||||
# <code>false</code>, which means that the language file is read when
|
|
||||||
# VIVO starts up, or when a new theme is selected.
|
|
||||||
#
|
|
||||||
# developer.defeatI18nCache = true
|
|
||||||
|
|
||||||
#
|
|
||||||
# For developers only: Setting this option to "true" slows down Vitro performance.
|
|
||||||
#
|
|
||||||
# Add starting and ending delimiters to each Freemarker template, so you can see
|
|
||||||
# which template were invoked by viewing the generated HTML. The default is
|
|
||||||
# <code>false</code>.
|
|
||||||
#
|
|
||||||
# developer.insertFreemarkerDelimiters = true
|
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@ auth:ADMIN
|
||||||
auth:hasPermission simplePermission:UseMiscellaneousAdminPages ;
|
auth:hasPermission simplePermission:UseMiscellaneousAdminPages ;
|
||||||
auth:hasPermission simplePermission:UseSparqlQueryPage ;
|
auth:hasPermission simplePermission:UseSparqlQueryPage ;
|
||||||
auth:hasPermission simplePermission:PageViewableAdmin ;
|
auth:hasPermission simplePermission:PageViewableAdmin ;
|
||||||
|
auth:hasPermission simplePermission:EnableDeveloperPanel ;
|
||||||
|
|
||||||
# permissions for CURATOR and above.
|
# permissions for CURATOR and above.
|
||||||
auth:hasPermission simplePermission:EditOntology ;
|
auth:hasPermission simplePermission:EditOntology ;
|
||||||
|
|
|
@ -76,6 +76,8 @@ public class SimplePermission extends Permission {
|
||||||
NAMESPACE + "UseAdvancedDataToolsPages");
|
NAMESPACE + "UseAdvancedDataToolsPages");
|
||||||
public static final SimplePermission USE_SPARQL_QUERY_PAGE = new SimplePermission(
|
public static final SimplePermission USE_SPARQL_QUERY_PAGE = new SimplePermission(
|
||||||
NAMESPACE + "UseSparqlQueryPage");
|
NAMESPACE + "UseSparqlQueryPage");
|
||||||
|
public static final SimplePermission ENABLE_DEVELOPER_PANEL = new SimplePermission(
|
||||||
|
NAMESPACE + "EnableDeveloperPanel");
|
||||||
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------
|
// ----------------------------------------------------------------------
|
||||||
|
|
|
@ -18,7 +18,6 @@ import org.apache.commons.lang.StringUtils;
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
import edu.cornell.mannlib.vitro.webapp.config.ConfigurationProperties;
|
|
||||||
import edu.cornell.mannlib.vitro.webapp.config.RevisionInfoBean;
|
import edu.cornell.mannlib.vitro.webapp.config.RevisionInfoBean;
|
||||||
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
|
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
|
||||||
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.DelimitingTemplateLoader;
|
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.DelimitingTemplateLoader;
|
||||||
|
@ -27,6 +26,8 @@ import edu.cornell.mannlib.vitro.webapp.controller.freemarker.UrlBuilder;
|
||||||
import edu.cornell.mannlib.vitro.webapp.edit.n3editing.configuration.EditConfigurationConstants;
|
import edu.cornell.mannlib.vitro.webapp.edit.n3editing.configuration.EditConfigurationConstants;
|
||||||
import edu.cornell.mannlib.vitro.webapp.i18n.freemarker.I18nMethodModel;
|
import edu.cornell.mannlib.vitro.webapp.i18n.freemarker.I18nMethodModel;
|
||||||
import edu.cornell.mannlib.vitro.webapp.startup.StartupStatus;
|
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.web.directives.IndividualShortViewDirective;
|
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.UrlDirective;
|
||||||
import edu.cornell.mannlib.vitro.webapp.web.directives.WidgetDirective;
|
import edu.cornell.mannlib.vitro.webapp.web.directives.WidgetDirective;
|
||||||
|
@ -55,18 +56,17 @@ import freemarker.template.TemplateModelException;
|
||||||
* own locale, etc.
|
* own locale, etc.
|
||||||
*
|
*
|
||||||
* Each time a request asks for the configuration, check to see whether the
|
* Each time a request asks for the configuration, check to see whether the
|
||||||
* cache is still valid, and whether the theme has changed (needs a new
|
* cache is still valid, whether the theme has changed (needs a new
|
||||||
* TemplateLoader). Store the request info to the ThreadLocal.
|
* TemplateLoader), and whether the DeveloperSettings have changed (might need a
|
||||||
|
* new TemplateLoader). Store the request info to the ThreadLocal.
|
||||||
*/
|
*/
|
||||||
public abstract class FreemarkerConfiguration {
|
public abstract class FreemarkerConfiguration {
|
||||||
private static final Log log = LogFactory
|
private static final Log log = LogFactory
|
||||||
.getLog(FreemarkerConfiguration.class);
|
.getLog(FreemarkerConfiguration.class);
|
||||||
|
|
||||||
private static final String PROPERTY_DEFEAT_CACHE = "developer.defeatFreemarkerCache";
|
|
||||||
private static final String PROPERTY_INSERT_DELIMITERS = "developer.insertFreemarkerDelimiters";
|
|
||||||
|
|
||||||
private static volatile FreemarkerConfigurationImpl instance;
|
private static volatile FreemarkerConfigurationImpl instance;
|
||||||
private static volatile String previousThemeDir;
|
private static volatile String previousThemeDir;
|
||||||
|
private static volatile Map<String, Object> previousSettingsMap;
|
||||||
|
|
||||||
public static Configuration getConfig(HttpServletRequest req) {
|
public static Configuration getConfig(HttpServletRequest req) {
|
||||||
confirmInstanceIsSet();
|
confirmInstanceIsSet();
|
||||||
|
@ -92,14 +92,12 @@ public abstract class FreemarkerConfiguration {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** If the developer doesn't want the cache, it's invalid. */
|
||||||
private static boolean isTemplateCacheInvalid(HttpServletRequest req) {
|
private static boolean isTemplateCacheInvalid(HttpServletRequest req) {
|
||||||
ConfigurationProperties props = ConfigurationProperties.getBean(req);
|
DeveloperSettings settings = DeveloperSettings.getBean(req);
|
||||||
|
if (settings.getBoolean(Keys.DEFEAT_FREEMARKER_CACHE)) {
|
||||||
// If the developer doesn't want the cache, it's invalid.
|
|
||||||
if (Boolean.valueOf(props.getProperty(PROPERTY_DEFEAT_CACHE))) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,7 +111,8 @@ public abstract class FreemarkerConfiguration {
|
||||||
private static void keepTemplateLoaderCurrentWithThemeDirectory(
|
private static void keepTemplateLoaderCurrentWithThemeDirectory(
|
||||||
HttpServletRequest req) {
|
HttpServletRequest req) {
|
||||||
String themeDir = getThemeDirectory(req);
|
String themeDir = getThemeDirectory(req);
|
||||||
if (hasThemeDirectoryChanged(themeDir)) {
|
if (hasThemeDirectoryChanged(themeDir)
|
||||||
|
|| haveDeveloperSettingsChanged(req)) {
|
||||||
TemplateLoader tl = createTemplateLoader(req, themeDir);
|
TemplateLoader tl = createTemplateLoader(req, themeDir);
|
||||||
instance.setTemplateLoader(tl);
|
instance.setTemplateLoader(tl);
|
||||||
}
|
}
|
||||||
|
@ -134,10 +133,20 @@ public abstract class FreemarkerConfiguration {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean haveDeveloperSettingsChanged(HttpServletRequest req) {
|
||||||
|
Map<String, Object> settingsMap = DeveloperSettings.getBean(req)
|
||||||
|
.getSettingsMap();
|
||||||
|
if (settingsMap.equals(previousSettingsMap)) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
previousSettingsMap = settingsMap;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static TemplateLoader createTemplateLoader(HttpServletRequest req,
|
private static TemplateLoader createTemplateLoader(HttpServletRequest req,
|
||||||
String themeDir) {
|
String themeDir) {
|
||||||
ServletContext ctx = req.getSession().getServletContext();
|
ServletContext ctx = req.getSession().getServletContext();
|
||||||
ConfigurationProperties props = ConfigurationProperties.getBean(ctx);
|
|
||||||
|
|
||||||
List<TemplateLoader> loaders = new ArrayList<TemplateLoader>();
|
List<TemplateLoader> loaders = new ArrayList<TemplateLoader>();
|
||||||
|
|
||||||
|
@ -167,7 +176,8 @@ public abstract class FreemarkerConfiguration {
|
||||||
MultiTemplateLoader mtl = new MultiTemplateLoader(loaderArray);
|
MultiTemplateLoader mtl = new MultiTemplateLoader(loaderArray);
|
||||||
|
|
||||||
// If requested, add delimiters to the templates.
|
// If requested, add delimiters to the templates.
|
||||||
if (Boolean.valueOf(props.getProperty(PROPERTY_INSERT_DELIMITERS))) {
|
DeveloperSettings settings = DeveloperSettings.getBean(req);
|
||||||
|
if (settings.getBoolean(Keys.INSERT_FREEMARKER_DELIMITERS)) {
|
||||||
return new DelimitingTemplateLoader(mtl);
|
return new DelimitingTemplateLoader(mtl);
|
||||||
} else {
|
} else {
|
||||||
return mtl;
|
return mtl;
|
||||||
|
|
|
@ -287,6 +287,7 @@ public class FreemarkerConfigurationImpl extends Configuration {
|
||||||
urls.put("images", UrlBuilder.getUrl("/images"));
|
urls.put("images", UrlBuilder.getUrl("/images"));
|
||||||
urls.put("theme", UrlBuilder.getUrl(themeDir));
|
urls.put("theme", UrlBuilder.getUrl(themeDir));
|
||||||
urls.put("index", UrlBuilder.getUrl("/browse"));
|
urls.put("index", UrlBuilder.getUrl("/browse"));
|
||||||
|
urls.put("developerAjax", UrlBuilder.getUrl("/admin/developerAjax"));
|
||||||
|
|
||||||
return urls;
|
return urls;
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,8 +15,9 @@ import javax.servlet.http.HttpServletRequest;
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
import edu.cornell.mannlib.vitro.webapp.config.ConfigurationProperties;
|
|
||||||
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
|
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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides access to a bundle of text strings, based on the name of the bundle,
|
* Provides access to a bundle of text strings, based on the name of the bundle,
|
||||||
|
@ -31,7 +32,6 @@ public class I18n {
|
||||||
private static final Log log = LogFactory.getLog(I18n.class);
|
private static final Log log = LogFactory.getLog(I18n.class);
|
||||||
|
|
||||||
public static final String DEFAULT_BUNDLE_NAME = "all";
|
public static final String DEFAULT_BUNDLE_NAME = "all";
|
||||||
private static final String PROPERTY_DEVELOPER_DEFEAT_CACHE = "developer.defeatI18nCache";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If this attribute is present on the request, then the cache has already
|
* If this attribute is present on the request, then the cache has already
|
||||||
|
@ -103,6 +103,7 @@ public class I18n {
|
||||||
protected I18nBundle getBundle(String bundleName, HttpServletRequest req) {
|
protected I18nBundle getBundle(String bundleName, HttpServletRequest req) {
|
||||||
log.debug("Getting bundle '" + bundleName + "'");
|
log.debug("Getting bundle '" + bundleName + "'");
|
||||||
|
|
||||||
|
I18nLogger i18nLogger = new I18nLogger(req);
|
||||||
try {
|
try {
|
||||||
checkDevelopmentMode(req);
|
checkDevelopmentMode(req);
|
||||||
checkForChangeInThemeDirectory(req);
|
checkForChangeInThemeDirectory(req);
|
||||||
|
@ -113,13 +114,13 @@ public class I18n {
|
||||||
ResourceBundle.Control control = new ThemeBasedControl(ctx, dir);
|
ResourceBundle.Control control = new ThemeBasedControl(ctx, dir);
|
||||||
ResourceBundle rb = ResourceBundle.getBundle(bundleName,
|
ResourceBundle rb = ResourceBundle.getBundle(bundleName,
|
||||||
req.getLocale(), control);
|
req.getLocale(), control);
|
||||||
return new I18nBundle(bundleName, rb);
|
return new I18nBundle(bundleName, rb, i18nLogger);
|
||||||
} catch (MissingResourceException e) {
|
} catch (MissingResourceException e) {
|
||||||
log.warn("Didn't find text bundle '" + bundleName + "'");
|
log.warn("Didn't find text bundle '" + bundleName + "'");
|
||||||
return I18nBundle.emptyBundle(bundleName);
|
return I18nBundle.emptyBundle(bundleName, i18nLogger);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("Failed to create text bundle '" + bundleName + "'", e);
|
log.error("Failed to create text bundle '" + bundleName + "'", e);
|
||||||
return I18nBundle.emptyBundle(bundleName);
|
return I18nBundle.emptyBundle(bundleName, i18nLogger);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,11 +128,7 @@ public class I18n {
|
||||||
* If we are in development mode, clear the cache on each request.
|
* If we are in development mode, clear the cache on each request.
|
||||||
*/
|
*/
|
||||||
private void checkDevelopmentMode(HttpServletRequest req) {
|
private void checkDevelopmentMode(HttpServletRequest req) {
|
||||||
ConfigurationProperties bean = ConfigurationProperties.getBean(req);
|
if (DeveloperSettings.getBean(req).getBoolean(Keys.I18N_DEFEAT_CACHE) ) {
|
||||||
|
|
||||||
String flag = bean
|
|
||||||
.getProperty(PROPERTY_DEVELOPER_DEFEAT_CACHE, "false");
|
|
||||||
if (Boolean.valueOf(flag.trim())) {
|
|
||||||
log.debug("In development mode - clearing the cache.");
|
log.debug("In development mode - clearing the cache.");
|
||||||
clearCacheOnRequest(req);
|
clearCacheOnRequest(req);
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,24 +24,28 @@ public class I18nBundle {
|
||||||
private static final String MESSAGE_BUNDLE_NOT_FOUND = "Text bundle ''{0}'' not found.";
|
private static final String MESSAGE_BUNDLE_NOT_FOUND = "Text bundle ''{0}'' not found.";
|
||||||
private static final String MESSAGE_KEY_NOT_FOUND = "Text bundle ''{0}'' has no text for ''{1}''";
|
private static final String MESSAGE_KEY_NOT_FOUND = "Text bundle ''{0}'' has no text for ''{1}''";
|
||||||
|
|
||||||
public static I18nBundle emptyBundle(String bundleName) {
|
public static I18nBundle emptyBundle(String bundleName,
|
||||||
return new I18nBundle(bundleName);
|
I18nLogger i18nLogger) {
|
||||||
|
return new I18nBundle(bundleName, i18nLogger);
|
||||||
}
|
}
|
||||||
|
|
||||||
private final String bundleName;
|
private final String bundleName;
|
||||||
private final ResourceBundle resources;
|
private final ResourceBundle resources;
|
||||||
private final String notFoundMessage;
|
private final String notFoundMessage;
|
||||||
|
private final I18nLogger i18nLogger;
|
||||||
|
|
||||||
private I18nBundle(String bundleName) {
|
private I18nBundle(String bundleName, I18nLogger i18nLogger) {
|
||||||
this(bundleName, new EmptyResourceBundle(), MESSAGE_BUNDLE_NOT_FOUND);
|
this(bundleName, new EmptyResourceBundle(), MESSAGE_BUNDLE_NOT_FOUND,
|
||||||
|
i18nLogger);
|
||||||
}
|
}
|
||||||
|
|
||||||
public I18nBundle(String bundleName, ResourceBundle resources) {
|
public I18nBundle(String bundleName, ResourceBundle resources,
|
||||||
this(bundleName, resources, MESSAGE_KEY_NOT_FOUND);
|
I18nLogger i18nLogger) {
|
||||||
|
this(bundleName, resources, MESSAGE_KEY_NOT_FOUND, i18nLogger);
|
||||||
}
|
}
|
||||||
|
|
||||||
private I18nBundle(String bundleName, ResourceBundle resources,
|
private I18nBundle(String bundleName, ResourceBundle resources,
|
||||||
String notFoundMessage) {
|
String notFoundMessage, I18nLogger i18nLogger) {
|
||||||
if (bundleName == null) {
|
if (bundleName == null) {
|
||||||
throw new IllegalArgumentException("bundleName may not be null");
|
throw new IllegalArgumentException("bundleName may not be null");
|
||||||
}
|
}
|
||||||
|
@ -57,22 +61,27 @@ public class I18nBundle {
|
||||||
this.bundleName = bundleName;
|
this.bundleName = bundleName;
|
||||||
this.resources = resources;
|
this.resources = resources;
|
||||||
this.notFoundMessage = notFoundMessage;
|
this.notFoundMessage = notFoundMessage;
|
||||||
|
this.i18nLogger = i18nLogger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String text(String key, Object... parameters) {
|
public String text(String key, Object... parameters) {
|
||||||
|
|
||||||
String textString;
|
String textString;
|
||||||
if (resources.containsKey(key)) {
|
if (resources.containsKey(key)) {
|
||||||
textString = resources.getString(key);
|
textString = resources.getString(key);
|
||||||
log.debug("In '" + bundleName + "', " + key + "='" + textString
|
log.debug("In '" + bundleName + "', " + key + "='" + textString
|
||||||
+ "')");
|
+ "')");
|
||||||
return formatString(textString, parameters);
|
|
||||||
} else {
|
} else {
|
||||||
String message = MessageFormat.format(notFoundMessage, bundleName,
|
String message = MessageFormat.format(notFoundMessage, bundleName,
|
||||||
key);
|
key);
|
||||||
log.warn(message);
|
log.warn(message);
|
||||||
return "ERROR: " + message;
|
textString = "ERROR: " + message;
|
||||||
}
|
}
|
||||||
|
String result = formatString(textString, parameters);
|
||||||
|
|
||||||
|
if (i18nLogger != null) {
|
||||||
|
i18nLogger.log(bundleName, key, parameters, textString, result);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String formatString(String textString, Object... parameters) {
|
private static String formatString(String textString, Object... parameters) {
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If enabled in developer mode, write a message to the log each time someone
|
||||||
|
* asks for a language string.
|
||||||
|
*
|
||||||
|
* The I18nBundle has a life span of one HTTP request, and so does this.
|
||||||
|
*/
|
||||||
|
public class I18nLogger {
|
||||||
|
private static final Log log = LogFactory.getLog(I18nLogger.class);
|
||||||
|
|
||||||
|
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)
|
||||||
|
&& log.isInfoEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void log(String bundleName, String key, Object[] parameters,
|
||||||
|
String rawText, String formattedText) {
|
||||||
|
if (isLogging) {
|
||||||
|
String message = String.format(
|
||||||
|
"Retrieved from %s.%s with %s: '%s'", bundleName, key,
|
||||||
|
Arrays.toString(parameters), rawText);
|
||||||
|
|
||||||
|
if (!rawText.equals(formattedText)) {
|
||||||
|
message += String.format(" --> '%s'", formattedText);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,7 +15,8 @@ import org.apache.commons.lang.StringUtils;
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
import edu.cornell.mannlib.vitro.webapp.config.ConfigurationProperties;
|
import edu.cornell.mannlib.vitro.webapp.utils.developer.DeveloperSettings;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.utils.developer.DeveloperSettings.Keys;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Writes the log message for the LoggingRDFService.
|
* Writes the log message for the LoggingRDFService.
|
||||||
|
@ -41,10 +42,6 @@ import edu.cornell.mannlib.vitro.webapp.config.ConfigurationProperties;
|
||||||
public class RDFServiceLogger implements AutoCloseable {
|
public class RDFServiceLogger implements AutoCloseable {
|
||||||
private static final Log log = LogFactory.getLog(RDFServiceLogger.class);
|
private static final Log log = LogFactory.getLog(RDFServiceLogger.class);
|
||||||
|
|
||||||
private static final String PROPERTY_ENABLED = "developer.loggingRDFService.enable";
|
|
||||||
private static final String PROPERTY_STACK_TRACE = "developer.loggingRDFService.stackTrace";
|
|
||||||
private static final String PROPERTY_RESTRICTION = "developer.loggingRDFService.restriction";
|
|
||||||
|
|
||||||
private final ServletContext ctx;
|
private final ServletContext ctx;
|
||||||
private final Object[] args;
|
private final Object[] args;
|
||||||
|
|
||||||
|
@ -72,18 +69,21 @@ public class RDFServiceLogger implements AutoCloseable {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void getProperties() {
|
private void getProperties() {
|
||||||
ConfigurationProperties props = ConfigurationProperties.getBean(ctx);
|
DeveloperSettings settings = DeveloperSettings.getBean(ctx);
|
||||||
isEnabled = Boolean.valueOf(props.getProperty(PROPERTY_ENABLED));
|
isEnabled = settings.getBoolean(Keys.LOGGING_RDF_ENABLE);
|
||||||
traceRequested = Boolean.valueOf(props
|
traceRequested = settings.getBoolean(Keys.LOGGING_RDF_STACK_TRACE);
|
||||||
.getProperty(PROPERTY_STACK_TRACE));
|
|
||||||
|
|
||||||
String restrictionString = props.getProperty(PROPERTY_RESTRICTION);
|
String restrictionString = settings
|
||||||
if (StringUtils.isNotBlank(restrictionString)) {
|
.getString(Keys.LOGGING_RDF_RESTRICTION);
|
||||||
|
if (StringUtils.isBlank(restrictionString)) {
|
||||||
|
restriction = null;
|
||||||
|
} else {
|
||||||
try {
|
try {
|
||||||
restriction = Pattern.compile(restrictionString);
|
restriction = Pattern.compile(restrictionString);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("Failed to compile the pattern for "
|
log.error("Failed to compile the pattern for "
|
||||||
+ PROPERTY_RESTRICTION + " = " + restriction + " " + e);
|
+ Keys.LOGGING_RDF_RESTRICTION.key() + " = "
|
||||||
|
+ restriction + " " + e);
|
||||||
isEnabled = false;
|
isEnabled = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,251 @@
|
||||||
|
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
|
||||||
|
|
||||||
|
package edu.cornell.mannlib.vitro.webapp.utils.developer;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileReader;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.EnumMap;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
import javax.servlet.ServletContext;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.config.ConfigurationProperties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hold the global developer settings. Render to JSON when requested.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
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_RESTRICTION("developer.loggingRDFService.restriction",
|
||||||
|
false);
|
||||||
|
|
||||||
|
private final String key;
|
||||||
|
private final boolean bool;
|
||||||
|
|
||||||
|
Keys(String key, boolean bool) {
|
||||||
|
this.key = key;
|
||||||
|
this.bool = bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String key() {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isBoolean() {
|
||||||
|
return bool;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------
|
||||||
|
// The factory
|
||||||
|
// ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
private static final String ATTRIBUTE_NAME = DeveloperSettings.class
|
||||||
|
.getName();
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------
|
||||||
|
// The instance
|
||||||
|
// ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
private final Map<Keys, Object> settings = new EnumMap<>(Keys.class);
|
||||||
|
|
||||||
|
private DeveloperSettings(ServletContext ctx) {
|
||||||
|
updateFromFile(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<String, 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(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<String, String> fromRequest = new HashMap<>();
|
||||||
|
for (String key : parameterMap.keySet()) {
|
||||||
|
fromRequest.put(key, parameterMap.get(key)[0]);
|
||||||
|
}
|
||||||
|
update(fromRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void update(Map<String, String> changedSettings) {
|
||||||
|
for (Keys key : Keys.values()) {
|
||||||
|
String s = changedSettings.get(key.key());
|
||||||
|
if (s != null) {
|
||||||
|
if (key.isBoolean()) {
|
||||||
|
settings.put(key, Boolean.valueOf(s));
|
||||||
|
} else {
|
||||||
|
settings.put(key, s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 (!key.isBoolean()) {
|
||||||
|
throw new IllegalArgumentException("Key '" + 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getString(Keys key) {
|
||||||
|
if (key.isBoolean()) {
|
||||||
|
throw new IllegalArgumentException("Key '" + key.key()
|
||||||
|
+ "' takes a boolean value.");
|
||||||
|
}
|
||||||
|
if (settings.containsKey(key)) {
|
||||||
|
if (Boolean.TRUE.equals(settings.get(Keys.ENABLED))) {
|
||||||
|
return (String) settings.get(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> getSettingsMap() {
|
||||||
|
Map<String, Object> map = new HashMap<>();
|
||||||
|
for (Keys key : Keys.values()) {
|
||||||
|
map.put(key.key(), get(key));
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "DeveloperSettings" + settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* For debugging. */
|
||||||
|
private void dumpParameterMap(Map<String, String[]> parameterMap) {
|
||||||
|
Map<String, List<String>> map = new HashMap<>();
|
||||||
|
for (String key : parameterMap.keySet()) {
|
||||||
|
map.put(key, Arrays.asList(parameterMap.get(key)));
|
||||||
|
}
|
||||||
|
log.debug("Parameter map: " + map);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,93 @@
|
||||||
|
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
|
||||||
|
|
||||||
|
package edu.cornell.mannlib.vitro.webapp.utils.developer;
|
||||||
|
|
||||||
|
import static edu.cornell.mannlib.vitro.webapp.utils.developer.DeveloperSettings.Keys.PERMIT_ANONYMOUS_CONTROL;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.auth.permissions.SimplePermission;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.auth.policy.PolicyHelper;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.controller.ajax.VitroAjaxController;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.services.freemarker.FreemarkerProcessingService.TemplateProcessingException;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.services.freemarker.FreemarkerProcessingServiceSetup;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accept an AJAX request to update the developer settings. Return an HTML
|
||||||
|
* representation of the developer panel from the settings and a Freemarker
|
||||||
|
* template.
|
||||||
|
*
|
||||||
|
* If developer mode is not enabled, the HTML response is empty.
|
||||||
|
*
|
||||||
|
* You may only control the panel if you are logged in with sufficient
|
||||||
|
* authorization, or if anonymous control is permitted by the settings.
|
||||||
|
*
|
||||||
|
* If you are not allowed to control the panel, then the HTML response
|
||||||
|
* is only a statement that developer mode is enabled. Otherwise, it
|
||||||
|
* is a full panel (collapsed at first).
|
||||||
|
*/
|
||||||
|
public class DeveloperSettingsServlet extends VitroAjaxController {
|
||||||
|
private static final Log log = LogFactory
|
||||||
|
.getLog(DeveloperSettingsServlet.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doRequest(VitroRequest vreq, HttpServletResponse resp)
|
||||||
|
throws ServletException, IOException {
|
||||||
|
DeveloperSettings settings = DeveloperSettings.getBean(vreq);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Are they allowed to control the panel?
|
||||||
|
*/
|
||||||
|
if (isAuthorized(vreq)) {
|
||||||
|
// Update the settings.
|
||||||
|
settings.updateFromRequest(vreq.getParameterMap());
|
||||||
|
} else {
|
||||||
|
log.debug("Not authorized to update settings.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Build the response.
|
||||||
|
*/
|
||||||
|
try {
|
||||||
|
Map<String, Object> bodyMap = buildBodyMap(isAuthorized(vreq),
|
||||||
|
settings);
|
||||||
|
String rendered = renderTemplate(vreq, bodyMap);
|
||||||
|
resp.getWriter().write(rendered);
|
||||||
|
} catch (Exception e) {
|
||||||
|
doError(resp, e.toString(), 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> buildBodyMap(boolean authorized,
|
||||||
|
DeveloperSettings settings) {
|
||||||
|
Map<String, Object> settingsMap = new HashMap<>();
|
||||||
|
settingsMap.putAll(settings.getSettingsMap());
|
||||||
|
settingsMap.put("mayControl", authorized);
|
||||||
|
Map<String, Object> bodyMap = new HashMap<>();
|
||||||
|
bodyMap.put("settings", settingsMap);
|
||||||
|
return bodyMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String renderTemplate(VitroRequest vreq, Map<String, Object> bodyMap)
|
||||||
|
throws TemplateProcessingException {
|
||||||
|
return FreemarkerProcessingServiceSetup.getService(getServletContext())
|
||||||
|
.renderTemplate("developerPanel.ftl", bodyMap, vreq);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isAuthorized(VitroRequest vreq) {
|
||||||
|
boolean authBySetting = DeveloperSettings.getBean(vreq).getBoolean(
|
||||||
|
PERMIT_ANONYMOUS_CONTROL);
|
||||||
|
boolean authByPolicy = PolicyHelper.isAuthorizedForActions(vreq,
|
||||||
|
SimplePermission.ENABLE_DEVELOPER_PANEL.ACTION);
|
||||||
|
return authBySetting || authByPolicy;
|
||||||
|
}
|
||||||
|
}
|
|
@ -51,7 +51,7 @@ public class I18nStub extends I18n {
|
||||||
|
|
||||||
private class I18nBundleStub extends I18nBundle {
|
private class I18nBundleStub extends I18nBundle {
|
||||||
public I18nBundleStub(String bundleName) {
|
public I18nBundleStub(String bundleName) {
|
||||||
super(bundleName, new DummyResourceBundle());
|
super(bundleName, new DummyResourceBundle(), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
<#include "developer.ftl">
|
||||||
|
|
||||||
<nav role="navigation">
|
<nav role="navigation">
|
||||||
<ul id="main-nav" role="list">
|
<ul id="main-nav" role="list">
|
||||||
<#list menu.items as item>
|
<#list menu.items as item>
|
||||||
|
|
|
@ -814,6 +814,15 @@
|
||||||
<url-pattern>/searchHelp</url-pattern>
|
<url-pattern>/searchHelp</url-pattern>
|
||||||
</servlet-mapping>
|
</servlet-mapping>
|
||||||
|
|
||||||
|
<servlet>
|
||||||
|
<servlet-name>DeveloperAjax</servlet-name>
|
||||||
|
<servlet-class>edu.cornell.mannlib.vitro.webapp.utils.developer.DeveloperSettingsServlet</servlet-class>
|
||||||
|
</servlet>
|
||||||
|
<servlet-mapping>
|
||||||
|
<servlet-name>DeveloperAjax</servlet-name>
|
||||||
|
<url-pattern>/admin/developerAjax</url-pattern>
|
||||||
|
</servlet-mapping>
|
||||||
|
|
||||||
<!-- for now, need to make sure the links on CALS' site doesn't break -->
|
<!-- for now, need to make sure the links on CALS' site doesn't break -->
|
||||||
<servlet-mapping>
|
<servlet-mapping>
|
||||||
<servlet-name>SearchController</servlet-name>
|
<servlet-name>SearchController</servlet-name>
|
||||||
|
|
82
webapp/web/js/developer/developerPanel.js
Normal file
82
webapp/web/js/developer/developerPanel.js
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
|
||||||
|
|
||||||
|
function DeveloperPanel(developerAjaxUrl) {
|
||||||
|
this.setupDeveloperPanel = updateDeveloperPanel;
|
||||||
|
|
||||||
|
function updateDeveloperPanel(data) {
|
||||||
|
$.ajax({
|
||||||
|
url: developerAjaxUrl,
|
||||||
|
dataType: "json",
|
||||||
|
data: data,
|
||||||
|
complete: function(xhr, status) {
|
||||||
|
updatePanelContents(xhr.responseText);
|
||||||
|
if (document.getElementById("developerPanelSaveButton")) {
|
||||||
|
enablePanelOpener();
|
||||||
|
addBehaviorToElements();
|
||||||
|
updateDisabledFields();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updatePanelContents(contents) {
|
||||||
|
document.getElementById("developerPanel").innerHTML = contents;
|
||||||
|
}
|
||||||
|
|
||||||
|
function enablePanelOpener() {
|
||||||
|
document.getElementById("developerPanelClickMe").onclick = function() {
|
||||||
|
document.getElementById("developerPanelClickText").style.display = "none";
|
||||||
|
document.getElementById("developerPanelBody").style.display = "block";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function addBehaviorToElements() {
|
||||||
|
document.getElementById("developerPanelSaveButton").onclick = function() {
|
||||||
|
updateDeveloperPanel(collectFormData());
|
||||||
|
}
|
||||||
|
document.getElementById("developer.enabled").onchange = updateDisabledFields
|
||||||
|
document.getElementById("developer.loggingRDFService.enable").onchange = updateDisabledFields
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateDisabledFields() {
|
||||||
|
var developerEnabled = document.getElementById("developer.enabled").checked;
|
||||||
|
document.getElementById("developer.defeatFreemarkerCache").disabled = !developerEnabled;
|
||||||
|
document.getElementById("developer.insertFreemarkerDelimiters").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("developer.loggingRDFService.enable").checked;
|
||||||
|
document.getElementById("developer.loggingRDFService.stackTrace").disabled = !rdfServiceEnabled;
|
||||||
|
document.getElementById("developer.loggingRDFService.restriction").disabled = !rdfServiceEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
function collectFormData() {
|
||||||
|
var data = new Object();
|
||||||
|
data["developer.panelOpen"] = false;
|
||||||
|
getCheckbox("developer.enabled", data);
|
||||||
|
getCheckbox("developer.defeatFreemarkerCache", data);
|
||||||
|
getCheckbox("developer.insertFreemarkerDelimiters", data);
|
||||||
|
getCheckbox("developer.i18n.defeatCache", data);
|
||||||
|
getCheckbox("developer.i18n.logStringRequests", data);
|
||||||
|
getCheckbox("developer.loggingRDFService.enable", data);
|
||||||
|
getCheckbox("developer.loggingRDFService.stackTrace", data);
|
||||||
|
getText("developer.loggingRDFService.restriction", data);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCheckbox(key, dest) {
|
||||||
|
dest[key] = document.getElementById(key).checked;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getText(key, dest) {
|
||||||
|
dest[key] = document.getElementById(key).value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Relies on the global variable for the AJAX URL.
|
||||||
|
*/
|
||||||
|
$(document).ready(function() {
|
||||||
|
new DeveloperPanel(developerAjaxUrl).setupDeveloperPanel({});
|
||||||
|
});
|
|
@ -0,0 +1,5 @@
|
||||||
|
<#-- $This file is distributed under the terms of the license in /doc/license.txt$ -->
|
||||||
|
|
||||||
|
<div id="developerPanel" > </div>
|
||||||
|
<script>developerAjaxUrl = '${urls.developerAjax}'</script>
|
||||||
|
${scripts.add('<script type="text/javascript" src="${urls.base}/js/developer/developerPanel.js"></script>')}
|
|
@ -0,0 +1,91 @@
|
||||||
|
<#-- $This file is distributed under the terms of the license in /doc/license.txt$ -->
|
||||||
|
|
||||||
|
|
||||||
|
<#macro showCheckbox key>
|
||||||
|
<input type="checkbox" id="${key}" <#if settings[key]>checked</#if>>
|
||||||
|
</#macro>
|
||||||
|
|
||||||
|
<#macro showTextbox key>
|
||||||
|
<input type="text" id="${key}" size="40" value="${settings[key]}" >
|
||||||
|
</#macro>
|
||||||
|
|
||||||
|
|
||||||
|
<style>
|
||||||
|
div.developer {
|
||||||
|
background-color: red;
|
||||||
|
padding: 0px 10px 0px 10px;
|
||||||
|
font-size: small;
|
||||||
|
font-variant: small-caps;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.developer #developerPanelBody {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.developer .container {
|
||||||
|
border: thin groove black
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<#if !settings["developer.enabled"]>
|
||||||
|
<#elseif !settings["mayControl"]>
|
||||||
|
<div class="developer">
|
||||||
|
<h1>${siteName} is running in developer mode.</h1>
|
||||||
|
</div>
|
||||||
|
<#else>
|
||||||
|
<div class="developer">
|
||||||
|
<h1 id="developerPanelClickMe">${siteName} is running in developer mode.
|
||||||
|
<span id="developerPanelClickText">(click for Options)</span>
|
||||||
|
</h1>
|
||||||
|
<div id="developerPanelBody">
|
||||||
|
<form>
|
||||||
|
<label>
|
||||||
|
<@showCheckbox "developer.enabled" />
|
||||||
|
Enable developer mode
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
Freemarker templates
|
||||||
|
<label>
|
||||||
|
<@showCheckbox "developer.defeatFreemarkerCache" />
|
||||||
|
Defeat the template cache
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<@showCheckbox "developer.insertFreemarkerDelimiters" />
|
||||||
|
Insert HTML comments at start and end of templates
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
SPARQL Queries
|
||||||
|
<label>
|
||||||
|
<@showCheckbox "developer.loggingRDFService.enable" />
|
||||||
|
Log each query
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<@showCheckbox "developer.loggingRDFService.stackTrace" />
|
||||||
|
Add stack trace
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Restrict by calling stack
|
||||||
|
<@showTextbox "developer.loggingRDFService.restriction" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
Language support
|
||||||
|
<label>
|
||||||
|
<@showCheckbox "developer.i18n.defeatCache" />
|
||||||
|
Defeat the cache of language property files
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<@showCheckbox "developer.i18n.logStringRequests" />
|
||||||
|
Log the retrieval of language strings
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input type="button" id="developerPanelSaveButton" value="Save Settings" name="foo" />
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</#if>
|
|
@ -1,5 +1,7 @@
|
||||||
<#-- $This file is distributed under the terms of the license in /doc/license.txt$ -->
|
<#-- $This file is distributed under the terms of the license in /doc/license.txt$ -->
|
||||||
|
|
||||||
|
<#include "developer.ftl">
|
||||||
|
|
||||||
<nav role="navigation">
|
<nav role="navigation">
|
||||||
<ul id="main-nav" role="list">
|
<ul id="main-nav" role="list">
|
||||||
<#list menu.items as item>
|
<#list menu.items as item>
|
||||||
|
|
Loading…
Add table
Reference in a new issue