Merge branch 'maint-rel-1.6' of https://github.com/vivo-project/Vitro into maint-rel-1.6

This commit is contained in:
hudajkhan 2013-11-19 13:25:46 -05:00
commit 319c495617
37 changed files with 1584 additions and 524 deletions

View file

@ -745,6 +745,21 @@
</td> </td>
</tr> </tr>
<tr>
<td colspan="2">
Show only the most appropriate data values based on the Accept-Language
header supplied by the browser. Default is false if not set.
</td>
</tr>
<tr class="odd_row">
<td>
RDFService.languageFilter
</td>
<td>
false
</td>
</tr>
<tr> <tr>
<td colspan="2"> <td colspan="2">
Force VIVO to use a specific language or Locale instead of those Force VIVO to use a specific language or Locale instead of those
@ -780,65 +795,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>

View file

@ -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/**/*" />

View 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

View file

@ -116,9 +116,9 @@ proxy.eligibleTypeList = http://www.w3.org/2002/07/owl#Thing
# #
# Show only the most appropriate data values based on the Accept-Language # Show only the most appropriate data values based on the Accept-Language
# header supplied by the browser. Default is true if not set. # header supplied by the browser. Default is false if not set.
# #
RDFService.languageFilter = true # RDFService.languageFilter = true
# #
# Tell VIVO to generate HTTP headers on its responses to facilitate caching the # Tell VIVO to generate HTTP headers on its responses to facilitate caching the
@ -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

View file

@ -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 ;

View file

@ -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");
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------

View file

@ -57,7 +57,7 @@ public class Classes2ClassesOperationController extends BaseEditController {
return; return;
} }
VClassDao vcDao = request.getUnfilteredAssertionsWebappDaoFactory().getVClassDao(); VClassDao vcDao = request.getLanguageNeutralWebappDaoFactory().getVClassDao();
String modeStr = request.getParameter("opMode"); String modeStr = request.getParameter("opMode");
modeStr = (modeStr == null) ? "" : modeStr; modeStr = (modeStr == null) ? "" : modeStr;

View file

@ -38,6 +38,7 @@ public class DelimitingTemplateLoader implements TemplateLoader {
@Override @Override
public Object findTemplateSource(String name) throws IOException { public Object findTemplateSource(String name) throws IOException {
Object innerTS = innerLoader.findTemplateSource(name); Object innerTS = innerLoader.findTemplateSource(name);
log.debug("template source for '" + name + "' is '" + innerTS + "'");
if (innerTS == null) { if (innerTS == null) {
return null; return null;
} else { } else {

View file

@ -1,162 +0,0 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.webapp.controller.freemarker;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import freemarker.cache.TemplateLoader;
/**
* <p>
* A {@link TemplateLoader} that treats a directory and its sub-directories as a
* flat namespace.
* </p>
* <p>
* When a request is made to find a template source, the loader will search its
* base directory and any sub-directories for a file with a matching name. So a
* request for <code>myFile.ftl</code> might return a reference to a file at
* <code>base/myFile.ftl</code> or at <code>base/this/myFile.ftl</code>
* </p>
* <p>
* The order in which the sub-directories are searched is unspecified. The first
* matching file will be returned.
* </p>
* <p>
* A path (absolute or relative) on the source name would be meaningless, so any
* such path will be stripped before the search is made. That is, a request for
* <code>path/file.ftl</code> or <code>/absolute/path/file.ftl</code>is
* functionally identical to a request for <code>file.ftl</code>
* </p>
* <p>
* </p>
*/
public class FlatteningTemplateLoader implements TemplateLoader {
private static final Log log = LogFactory
.getLog(FlatteningTemplateLoader.class);
private final File baseDir;
public FlatteningTemplateLoader(File baseDir) {
if (baseDir == null) {
throw new NullPointerException("baseDir may not be null.");
}
if (!baseDir.exists()) {
throw new IllegalArgumentException("Template directory '"
+ baseDir.getAbsolutePath() + "' does not exist");
}
if (!baseDir.isDirectory()) {
throw new IllegalArgumentException("Template directory '"
+ baseDir.getAbsolutePath() + "' is not a directory");
}
if (!baseDir.canRead()) {
throw new IllegalArgumentException("Can't read template "
+ "directory '" + baseDir.getAbsolutePath() + "'");
}
log.debug("Created template loader - baseDir is '"
+ baseDir.getAbsolutePath() + "'");
this.baseDir = baseDir;
}
/**
* Look for a file by this name in the base directory, or its
* subdirectories, disregarding any path information.
*
* @return a {@link File} that can be used in subsequent calls the template
* loader methods, or <code>null</code> if no template is found.
*/
@Override
public Object findTemplateSource(String name) throws IOException {
if (name == null) {
return null;
}
int lastSlashHere = name.indexOf('/');
String trimmedName = (lastSlashHere == -1) ? name : name
.substring(lastSlashHere + 1);
// start the recursive search.
File source = findFile(trimmedName, baseDir);
if (source == null) {
log.debug("For template name '" + name
+ "', found no template file.");
} else {
log.debug("For template name '" + name + "', template file is "
+ source.getAbsolutePath());
}
return source;
}
/**
* Recursively search for a file of this name.
*/
private File findFile(String name, File dir) {
for (File child : dir.listFiles()) {
if (child.isDirectory()) {
File file = findFile(name, child);
if (file != null) {
return file;
}
} else {
if (child.getName().equals(name)) {
return child;
}
}
}
return null;
}
/**
* Ask the file when it was last modified.
*
* @param templateSource
* a {@link File} that was obtained earlier from
* {@link #findTemplateSource(String)}.
*/
@Override
public long getLastModified(Object templateSource) {
if (!(templateSource instanceof File)) {
throw new IllegalArgumentException("templateSource is not a File: "
+ templateSource);
}
return ((File) templateSource).lastModified();
}
/**
* Get a {@link Reader} on this {@link File}. The framework will see that
* the {@link Reader} is closed when it has been read.
*
* @param templateSource
* a {@link File} that was obtained earlier from
* {@link #findTemplateSource(String)}.
*/
@Override
public Reader getReader(Object templateSource, String encoding)
throws IOException {
if (!(templateSource instanceof File)) {
throw new IllegalArgumentException("templateSource is not a File: "
+ templateSource);
}
return new FileReader(((File) templateSource));
}
/**
* Nothing to do here. No resources to free up.
*
* @param templateSource
* a {@link File} that was obtained earlier from
* {@link #findTemplateSource(String)}.
*/
@Override
public void closeTemplateSource(Object templateSource) throws IOException {
}
}

View file

@ -1048,11 +1048,20 @@ public class VClassDaoJena extends JenaBaseDao implements VClassDao {
try { try {
OntResource subclass = getOntClass(ontModel,c2c.getSubclassURI()); OntResource subclass = getOntClass(ontModel,c2c.getSubclassURI());
OntResource superclass = getOntClass(ontModel,c2c.getSuperclassURI()); OntResource superclass = getOntClass(ontModel,c2c.getSuperclassURI());
if(subclass == null || superclass == null) {
log.warn("unable to delete " + c2c.getSubclassURI() +
" rdfs:subClassOf " + c2c.getSuperclassURI());
if (subclass == null) {
log.warn(c2c.getSubclassURI() + " not found in the model.");
}
if (superclass == null) {
log.warn(c2c.getSuperclassURI() + " not found in the model.");
}
return;
}
Model removal = ModelFactory.createDefaultModel(); Model removal = ModelFactory.createDefaultModel();
Model additions = ModelFactory.createDefaultModel(); // to repair any rdf:Lists Model additions = ModelFactory.createDefaultModel(); // to repair any rdf:Lists
if ((subclass != null) && (superclass != null)) { removal.add(ontModel.listStatements(subclass, RDFS.subClassOf, superclass));
removal.add(ontModel.listStatements(subclass, RDFS.subClassOf, superclass));
}
if (subclass.isAnon()) { if (subclass.isAnon()) {
Model[] changeSet = getSmartRemoval(subclass, getOntModel()); Model[] changeSet = getSmartRemoval(subclass, getOntModel());
removal.add(changeSet[0]); removal.add(changeSet[0]);

View file

@ -336,11 +336,11 @@ public class RequestModelsPrep implements Filter {
} }
/** /**
* Language awareness is enabled unless they explicitly disable it. * Language awareness is disabled unless they explicitly enable it.
*/ */
private Boolean isLanguageAwarenessEnabled() { private Boolean isLanguageAwarenessEnabled() {
return Boolean.valueOf(props.getProperty("RDFService.languageFilter", return Boolean.valueOf(props.getProperty("RDFService.languageFilter",
"true")); "false"));
} }
private RDFService addLanguageAwareness(HttpServletRequest req, private RDFService addLanguageAwareness(HttpServletRequest req,

View file

@ -3,7 +3,6 @@
package edu.cornell.mannlib.vitro.webapp.freemarker.config; package edu.cornell.mannlib.vitro.webapp.freemarker.config;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -18,15 +17,16 @@ 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;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.FlatteningTemplateLoader;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.UrlBuilder; 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.freemarker.loader.FreemarkerTemplateLoader;
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;
@ -34,7 +34,6 @@ import edu.cornell.mannlib.vitro.webapp.web.methods.IndividualLocalNameMethod;
import edu.cornell.mannlib.vitro.webapp.web.methods.IndividualPlaceholderImageUrlMethod; import edu.cornell.mannlib.vitro.webapp.web.methods.IndividualPlaceholderImageUrlMethod;
import edu.cornell.mannlib.vitro.webapp.web.methods.IndividualProfileUrlMethod; import edu.cornell.mannlib.vitro.webapp.web.methods.IndividualProfileUrlMethod;
import freemarker.cache.ClassTemplateLoader; import freemarker.cache.ClassTemplateLoader;
import freemarker.cache.FileTemplateLoader;
import freemarker.cache.MultiTemplateLoader; import freemarker.cache.MultiTemplateLoader;
import freemarker.cache.TemplateLoader; import freemarker.cache.TemplateLoader;
import freemarker.ext.beans.BeansWrapper; import freemarker.ext.beans.BeansWrapper;
@ -55,18 +54,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 +90,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 +109,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,44 +131,48 @@ 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>();
// Theme template loader // Theme template loader - only if the theme has a template directory.
String themeTemplatePath = ctx.getRealPath(themeDir) + "/templates"; String themeTemplatePath = ctx.getRealPath(themeDir) + "/templates";
File themeTemplateDir = new File(themeTemplatePath); File themeTemplateDir = new File(themeTemplatePath);
// A theme need not contain a template directory.
if (themeTemplateDir.exists()) { if (themeTemplateDir.exists()) {
try { loaders.add(new FreemarkerTemplateLoader(themeTemplateDir));
FileTemplateLoader themeFtl = new FileTemplateLoader(
themeTemplateDir);
loaders.add(themeFtl);
} catch (IOException e) {
log.error("Error creating theme template loader", e);
}
} }
// Vitro template loader // Vitro template loader
String vitroTemplatePath = ctx.getRealPath("/templates/freemarker"); String vitroTemplatePath = ctx.getRealPath("/templates/freemarker");
loaders.add(new FlatteningTemplateLoader(new File(vitroTemplatePath))); loaders.add(new FreemarkerTemplateLoader(new File(vitroTemplatePath)));
// TODO VIVO-243 Why is this here? // TODO VIVO-243 Why is this here?
loaders.add(new ClassTemplateLoader(FreemarkerConfiguration.class, "")); loaders.add(new ClassTemplateLoader(FreemarkerConfiguration.class, ""));
TemplateLoader[] loaderArray = loaders TemplateLoader[] loaderArray = loaders
.toArray(new TemplateLoader[loaders.size()]); .toArray(new TemplateLoader[loaders.size()]);
MultiTemplateLoader mtl = new MultiTemplateLoader(loaderArray); TemplateLoader tl = 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);
return new DelimitingTemplateLoader(mtl); if (settings.getBoolean(Keys.INSERT_FREEMARKER_DELIMITERS)) {
} else { tl = new DelimitingTemplateLoader(tl);
return mtl;
} }
return tl;
} }
private static void setThreadLocalsForRequest(HttpServletRequest req) { private static void setThreadLocalsForRequest(HttpServletRequest req) {

View file

@ -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;
} }

View file

@ -0,0 +1,314 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.webapp.freemarker.loader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Comparator;
import java.util.SortedSet;
import java.util.TreeSet;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import freemarker.cache.TemplateLoader;
/**
* Loads Freemarker templates from a given directory.
*
* Different from a file loader in two ways:
*
* 1) Flattens the directory. When it searches for a template, it will look in
* the base directory and in any sub-directories. While doing this, it ignores
* any path that is attached to the template name.
*
* So if you were to ask for 'admin/silly.ftl', it would search for 'silly.ftl'
* in the base directory, and in any sub-directories, until it finds one.
*
* 2) Accepts approximate matches on locales. When asked for a template, it will
* accepts an approximate match that matches the basename and extension, and
* language or region if specifed. So a search for a template with no language
* or region will prefer an exact match, but will accept one with language or
* both language and region.
*
* <pre>
* "this_es_MX.ftl" matches "this_es_MX.ftl"
* "this_es.ftl" matches "this_es.ftl" or "this_es_MX.ftl"
* "this.ftl" matches "this.ftl" or "this_es.ftl" or "this_es_MX.ftl"
* </pre>
*
* This allows Freemarker to mimic the behavior of the language filtering RDF
* service, because if Freemarker does not find a match for "this_es_MX.ftl", it
* will try again with "this_es.ftl" and "this.ftl". So the net effect is that a
* search for "silly_es_MX.ftl" would eventually return any of these, in order
* of preference:
*
* <pre>
* silly_es_MX.ftl
* silly_es.ftl
* silly_es_*.ftl
* silly.ftl
* silly_*.ftl
* </pre>
*
* If more than one template file qualifies, we choose by best fit, shortest
* path, and alphabetical order, to insure that identical requests produce
* identical results.
*/
public class FreemarkerTemplateLoader implements TemplateLoader {
private static final Log log = LogFactory
.getLog(FreemarkerTemplateLoader.class);
private final File baseDir;
public FreemarkerTemplateLoader(File baseDir) {
if (baseDir == null) {
throw new NullPointerException("baseDir may not be null.");
}
String path = baseDir.getAbsolutePath();
if (!baseDir.exists()) {
throw new IllegalArgumentException("Template directory '" + path
+ "' does not exist");
}
if (!baseDir.isDirectory()) {
throw new IllegalArgumentException("Template directory '" + path
+ "' is not a directory");
}
if (!baseDir.canRead()) {
throw new IllegalArgumentException(
"Can't read template directory '" + path + "'");
}
log.debug("Created template loader - baseDir is '" + path + "'");
this.baseDir = baseDir;
}
/**
* Get the best template for this name. Walk the tree finding all possible
* matches, then choose our favorite.
*/
@Override
public Object findTemplateSource(String name) throws IOException {
if (StringUtils.isBlank(name)) {
return null;
}
SortedSet<PathPieces> matches = findAllMatches(new PathPieces(name));
if (matches.isEmpty()) {
return null;
} else {
return matches.last().path.toFile();
}
}
private SortedSet<PathPieces> findAllMatches(PathPieces searchTerm) {
PathPiecesFileVisitor visitor = new PathPiecesFileVisitor(searchTerm);
try {
Files.walkFileTree(baseDir.toPath(), visitor);
} catch (IOException e) {
log.error(e);
}
return visitor.getMatches();
}
/**
* Ask the file when it was last modified.
*
* @param templateSource
* a File that was obtained earlier from findTemplateSource().
*/
@Override
public long getLastModified(Object templateSource) {
return asFile(templateSource).lastModified();
}
/**
* Get a Reader on this File. The framework will close the Reader after
* reading it.
*
* @param templateSource
* a File that was obtained earlier from findTemplateSource().
*/
@Override
public Reader getReader(Object templateSource, String encoding)
throws IOException {
return new FileReader(asFile(templateSource));
}
/**
* Nothing to do here. No resources to free up.
*
* @param templateSource
* a File that was obtained earlier from findTemplateSource().
*/
@Override
public void closeTemplateSource(Object templateSource) throws IOException {
// Nothing to do.
}
/**
* That templateSource is a File, right?
*/
private File asFile(Object templateSource) {
if (templateSource instanceof File) {
return (File) templateSource;
} else {
throw new IllegalArgumentException("templateSource is not a File: "
+ templateSource);
}
}
// ----------------------------------------------------------------------
// Helper classes
// ----------------------------------------------------------------------
/**
* Break a path into handy segments, so we can see whether they match the
* search term, and how well they match.
*/
static class PathPieces {
final Path path;
final String base;
final String language;
final String region;
final String extension;
public PathPieces(String searchTerm) {
this(Paths.get(searchTerm));
}
public PathPieces(Path path) {
this.path = path;
String filename = path.getFileName().toString();
int dotHere = filename.lastIndexOf('.');
String basename;
if (dotHere != -1) {
basename = filename.substring(0, dotHere);
this.extension = filename.substring(dotHere);
} else {
basename = filename;
this.extension = "";
}
int break2 = basename.lastIndexOf('_');
int break1 = basename.lastIndexOf('_', break2 - 1);
if (break1 != -1) {
this.base = basename.substring(0, break1);
this.language = basename.substring(break1, break2);
this.region = basename.substring(break2);
} else if (break2 != -1) {
this.base = basename.substring(0, break2);
this.language = basename.substring(break2);
this.region = "";
} else {
this.base = basename;
this.language = "";
this.region = "";
}
}
/** This is the search term. Does that candidate qualify as a result? */
public boolean matches(PathPieces that) {
return base.equals(that.base) && extension.equals(that.extension)
&& (language.isEmpty() || language.equals(that.language))
&& (region.isEmpty() || region.equals(that.region));
}
public int score(PathPieces that) {
if (matches(that)) {
if (that.language.equals(language)) {
if (that.region.equals(region)) {
return 3; // match language and region
} else {
return 2; // match language, default region.
}
} else {
return 1; // default language.
}
} else {
return -1; // doesn't match.
}
}
@Override
public String toString() {
return "PathPieces[" + base + ", " + language + ", " + region
+ ", " + extension + "]";
}
}
/**
* While walking the file tree, collect all files that match the search
* term, as a sorted set of PathPieces.
*/
static class PathPiecesFileVisitor extends SimpleFileVisitor<Path> {
private final PathPieces searchTerm;
private final SortedSet<PathPieces> matches;
public PathPiecesFileVisitor(PathPieces searchTerm) {
this.searchTerm = searchTerm;
this.matches = new TreeSet<>(new PathPiecesComparator(searchTerm));
}
@Override
public FileVisitResult visitFile(Path path, BasicFileAttributes attrs)
throws IOException {
if (fileQualifies(path)) {
PathPieces found = new PathPieces(path);
if (searchTerm.matches(found)) {
matches.add(found);
}
}
return FileVisitResult.CONTINUE;
}
public boolean fileQualifies(Path path) {
return Files.isRegularFile(path) && Files.isReadable(path);
}
public SortedSet<PathPieces> getMatches() {
return matches;
}
}
/**
* Produce an ordering of paths by desirability. Best match, then shortest
* directory path, and finally alphabetical order.
*/
static class PathPiecesComparator implements Comparator<PathPieces> {
private final PathPieces searchFor;
public PathPiecesComparator(PathPieces searchFor) {
this.searchFor = searchFor;
}
@Override
public int compare(PathPieces p1, PathPieces p2) {
int scoring = searchFor.score(p1) - searchFor.score(p2);
if (scoring != 0) {
return scoring; // prefer matches to region and language
}
int pathLength = p1.path.getNameCount() - p2.path.getNameCount();
if (pathLength != 0) {
return -pathLength; // shorter is better
}
return -p1.path.compareTo(p2.path); // early in alphabet is better
}
}
}

View file

@ -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);
} }

View file

@ -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) {

View file

@ -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);
}
}
}

View file

@ -207,6 +207,14 @@ public class KnowledgeBaseUpdater {
StmtIterator sit = anonModel.listStatements(); StmtIterator sit = anonModel.listStatements();
while (sit.hasNext()) { while (sit.hasNext()) {
Statement stmt = sit.nextStatement(); Statement stmt = sit.nextStatement();
// Skip statements with blank nodes (unsupported) to avoid
// excessive deletion. In the future, the whole updater
// could be modified to change whole graphs at once through
// the RDFService, but right now this whole thing is statement
// based.
if (stmt.getSubject().isAnon() || stmt.getObject().isAnon()) {
continue;
}
Iterator<String> graphIt = dataset.listNames(); Iterator<String> graphIt = dataset.listNames();
while(graphIt.hasNext()) { while(graphIt.hasNext()) {
String graph = graphIt.next(); String graph = graphIt.next();
@ -223,8 +231,9 @@ public class KnowledgeBaseUpdater {
//log.info("removed " + anonModel.size() + " statements from SPARQL CONSTRUCTs"); //log.info("removed " + anonModel.size() + " statements from SPARQL CONSTRUCTs");
} else { } else {
Model writeModel = dataset.getNamedModel(JenaDataSourceSetupBase.JENA_DB_MODEL); Model writeModel = dataset.getNamedModel(JenaDataSourceSetupBase.JENA_DB_MODEL);
Model dedupeModel = dataset.getDefaultModel();
Model additions = jiu.renameBNodes( Model additions = jiu.renameBNodes(
anonModel, settings.getDefaultNamespace() + "n", writeModel); anonModel, settings.getDefaultNamespace() + "n", dedupeModel);
Model actualAdditions = ModelFactory.createDefaultModel(); Model actualAdditions = ModelFactory.createDefaultModel();
StmtIterator stmtIt = additions.listStatements(); StmtIterator stmtIt = additions.listStatements();
while (stmtIt.hasNext()) { while (stmtIt.hasNext()) {

View file

@ -217,6 +217,9 @@ public abstract class RDFServiceJena extends RDFServiceImpl implements RDFServic
private List<Statement> sort(List<Statement> stmts) { private List<Statement> sort(List<Statement> stmts) {
List<Statement> output = new ArrayList<Statement>(); List<Statement> output = new ArrayList<Statement>();
int originalSize = stmts.size(); int originalSize = stmts.size();
if(originalSize == 1) {
return stmts;
}
List <Statement> remaining = stmts; List <Statement> remaining = stmts;
ConcurrentLinkedQueue<Resource> subjQueue = new ConcurrentLinkedQueue<Resource>(); ConcurrentLinkedQueue<Resource> subjQueue = new ConcurrentLinkedQueue<Resource>();
for(Statement stmt : remaining) { for(Statement stmt : remaining) {

View file

@ -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 + " = " + restriction
+ " " + e);
isEnabled = false; isEnabled = false;
} }
} }

View file

@ -744,6 +744,8 @@ public class RDFServiceSparql extends RDFServiceImpl implements RDFService {
private List<Statement> sort(List<Statement> stmts) { private List<Statement> sort(List<Statement> stmts) {
List<Statement> output = new ArrayList<Statement>(); List<Statement> output = new ArrayList<Statement>();
int originalSize = stmts.size(); int originalSize = stmts.size();
if (originalSize == 1)
return stmts;
List <Statement> remaining = stmts; List <Statement> remaining = stmts;
ConcurrentLinkedQueue<com.hp.hpl.jena.rdf.model.Resource> subjQueue = ConcurrentLinkedQueue<com.hp.hpl.jena.rdf.model.Resource> subjQueue =
new ConcurrentLinkedQueue<com.hp.hpl.jena.rdf.model.Resource>(); new ConcurrentLinkedQueue<com.hp.hpl.jena.rdf.model.Resource>();

View file

@ -3,6 +3,7 @@
package edu.cornell.mannlib.vitro.webapp.reasoner; package edu.cornell.mannlib.vitro.webapp.reasoner;
import java.io.InputStream; import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
@ -295,14 +296,35 @@ public class ABoxRecomputer {
*/ */
protected Collection<String> getAllIndividualURIs() { protected Collection<String> getAllIndividualURIs() {
String queryString = "SELECT DISTINCT ?s WHERE { GRAPH ?g { ?s a ?type } " + HashSet<String> individualURIs = new HashSet<String>();
" FILTER (!bound(?g) || !regex(str(?g),\"tbox\")) } ORDER BY ?s";
return getIndividualURIs(queryString); List<String> classList = new ArrayList<String>();
tboxModel.enterCriticalSection(Lock.READ);
try {
StmtIterator classIt = tboxModel.listStatements(
(Resource) null, RDF.type, OWL.Class);
while(classIt.hasNext()) {
Statement stmt = classIt.nextStatement();
if(stmt.getSubject().isURIResource()
&& stmt.getSubject().getURI() != null
&& !stmt.getSubject().getURI().isEmpty()) {
classList.add(stmt.getSubject().getURI());
}
}
} finally {
tboxModel.leaveCriticalSection();
}
for (String classURI : classList) {
String queryString = "SELECT ?s WHERE { ?s a <" + classURI + "> } ";
getIndividualURIs(queryString, individualURIs);
}
return individualURIs;
} }
protected Collection<String> getIndividualURIs(String queryString) { protected void getIndividualURIs(String queryString, Set<String> individuals) {
Set<String> individuals = new HashSet<String>();
int batchSize = 50000; int batchSize = 50000;
int offset = 0; int offset = 0;
@ -342,7 +364,6 @@ public class ABoxRecomputer {
offset += batchSize; offset += batchSize;
} }
return individuals;
} }
protected void addedABoxTypeAssertion(Resource individual, Model inferenceModel, HashSet<String> unknownTypes) { protected void addedABoxTypeAssertion(Resource individual, Model inferenceModel, HashSet<String> unknownTypes) {
@ -410,6 +431,11 @@ public class ABoxRecomputer {
while (iter.hasNext()) { while (iter.hasNext()) {
Statement stmt = iter.next(); Statement stmt = iter.next();
// skip statements with blank nodes to avoid excessive deletion
if (stmt.getSubject().isAnon() || stmt.getObject().isAnon()) {
continue;
}
inferenceModel.enterCriticalSection(Lock.WRITE); inferenceModel.enterCriticalSection(Lock.WRITE);
try { try {
inferenceModel.remove(stmt); inferenceModel.remove(stmt);

View file

@ -187,8 +187,7 @@ public class FileGraphSetup implements ServletContextListener {
} else { } else {
baseModel.add(model); baseModel.add(model);
} }
log.info("Attached file graph as " + type + " submodel " + p.getFileName()); log.debug("Attached file graph as " + type + " submodel " + p.getFileName());
} }
modelChanged = modelChanged | updateGraphInDB(dataset, model, type, p); modelChanged = modelChanged | updateGraphInDB(dataset, model, type, p);

View file

@ -221,7 +221,7 @@ public class JenaDataSourceSetupBase extends JenaBaseDaoCon {
int[] maxActiveAndIdle = getMaxActiveAndIdle(ctx); int[] maxActiveAndIdle = getMaxActiveAndIdle(ctx);
cpds.setMaxPoolSize(maxActiveAndIdle[0]); cpds.setMaxPoolSize(maxActiveAndIdle[0]);
cpds.setMinPoolSize(maxActiveAndIdle[1]); cpds.setMinPoolSize(maxActiveAndIdle[1]);
cpds.setMaxIdleTime(3600); // ms cpds.setMaxIdleTime(43200); // s
cpds.setMaxIdleTimeExcessConnections(300); cpds.setMaxIdleTimeExcessConnections(300);
cpds.setAcquireIncrement(5); cpds.setAcquireIncrement(5);
cpds.setNumHelperThreads(6); cpds.setNumHelperThreads(6);

View file

@ -0,0 +1,305 @@
/* $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 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
// ----------------------------------------------------------------------
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<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<>();
for (String key : parameterMap.keySet()) {
fromRequest.put(Keys.fromElementId(key), parameterMap.get(key)[0]);
}
update(fromRequest);
}
private void update(Map<Keys, String> changedSettings) {
for (Keys key : Keys.values()) {
String s = changedSettings.get(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
+ "' 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
+ "' 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.elementId(), 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);
}
}

View file

@ -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;
}
}

View file

@ -1,183 +0,0 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.webapp.controller.freemarker;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.io.IOException;
import java.io.Reader;
import java.text.SimpleDateFormat;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import edu.cornell.mannlib.vitro.testing.AbstractTestClass;
/**
* Test the methods of {@link FlatteningTemplateLoader}.
*/
public class FlatteningTemplateLoaderTest extends AbstractTestClass {
/**
* TODO test plan
*
* <pre>
* findTemplateSource
* null arg
* not found
* found in top level
* found in lower level
* with path
*
* getReader
* get it, read it, check it, close it.
*
* getLastModified
* check the create date within a range
* modify it and check again.
*
* </pre>
*/
// ----------------------------------------------------------------------
// setup and teardown
// ----------------------------------------------------------------------
private static final String SUBDIRECTORY_NAME = "sub";
private static final String TEMPLATE_NAME_UPPER = "template.ftl";
private static final String TEMPLATE_NAME_UPPER_WITH_PATH = "path/template.ftl";
private static final String TEMPLATE_UPPER_CONTENTS = "The contents of the file.";
private static final String TEMPLATE_NAME_LOWER = "another.ftl";
private static final String TEMPLATE_LOWER_CONTENTS = "Another template file.";
private static File tempDir;
private static File notADirectory;
private static File upperTemplate;
private static File lowerTemplate;
private FlatteningTemplateLoader loader;
@BeforeClass
public static void setUpFiles() throws IOException {
notADirectory = File.createTempFile(
FlatteningTemplateLoader.class.getSimpleName(), "");
tempDir = createTempDirectory(FlatteningTemplateLoader.class
.getSimpleName());
upperTemplate = createFile(tempDir, TEMPLATE_NAME_UPPER,
TEMPLATE_UPPER_CONTENTS);
File subdirectory = new File(tempDir, SUBDIRECTORY_NAME);
subdirectory.mkdir();
lowerTemplate = createFile(subdirectory, TEMPLATE_NAME_LOWER,
TEMPLATE_LOWER_CONTENTS);
}
@Before
public void initializeLoader() {
loader = new FlatteningTemplateLoader(tempDir);
}
@AfterClass
public static void cleanUpFiles() throws IOException {
purgeDirectoryRecursively(tempDir);
}
// ----------------------------------------------------------------------
// the tests
// ----------------------------------------------------------------------
@Test(expected = NullPointerException.class)
public void constructorNull() {
new FlatteningTemplateLoader(null);
}
@Test(expected = IllegalArgumentException.class)
public void constructorNonExistent() {
new FlatteningTemplateLoader(new File("bogusDirName"));
}
@Test(expected = IllegalArgumentException.class)
public void constructorNotADirectory() {
new FlatteningTemplateLoader(notADirectory);
}
@Test
public void findNull() throws IOException {
Object source = loader.findTemplateSource(null);
assertNull("find null", source);
}
@Test
public void findNotFound() throws IOException {
Object source = loader.findTemplateSource("bogus");
assertNull("not found", source);
}
@Test
public void findInTopLevel() throws IOException {
Object source = loader.findTemplateSource(TEMPLATE_NAME_UPPER);
assertEquals("top level", upperTemplate, source);
}
@Test
public void findInLowerLevel() throws IOException {
Object source = loader.findTemplateSource(TEMPLATE_NAME_LOWER);
assertEquals("lower level", lowerTemplate, source);
}
@Test
public void findIgnoringPath() throws IOException {
Object source = loader
.findTemplateSource(TEMPLATE_NAME_UPPER_WITH_PATH);
assertEquals("top level", upperTemplate, source);
}
@Test
public void checkTheReader() throws IOException {
Object source = loader.findTemplateSource(TEMPLATE_NAME_UPPER);
Reader reader = loader.getReader(source, "UTF-8");
String contents = readAll(reader);
assertEquals("read the contents", contents, TEMPLATE_UPPER_CONTENTS);
}
/**
* Some systems only record last-modified times to the nearest second, so we
* can't rely on them changing during the course of the test. Force the
* change, and test for it.
*/
@Test
public void teplateLastModified() throws IOException {
Object source = loader.findTemplateSource(TEMPLATE_NAME_UPPER);
long modified = loader.getLastModified(source);
long now = System.currentTimeMillis();
assertTrue("near to now: modified=" + formatTimeStamp(modified)
+ ", now=" + formatTimeStamp(now),
2000 > Math.abs(modified - now));
upperTemplate.setLastModified(5000);
assertEquals("modified modified", 5000, loader.getLastModified(source));
}
@Test
public void closeDoesntCrash() throws IOException {
Object source = loader.findTemplateSource(TEMPLATE_NAME_UPPER);
loader.closeTemplateSource(source);
}
// ----------------------------------------------------------------------
// helper methods
// ----------------------------------------------------------------------
private String formatTimeStamp(long time) {
SimpleDateFormat formatter = new SimpleDateFormat(
"yyyy-MM-dd HH:mm:ss.SSS");
return formatter.format(time);
}
}

View file

@ -0,0 +1,359 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.webapp.freemarker.loader;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.SortedSet;
import org.apache.commons.lang.StringUtils;
import org.junit.Test;
import edu.cornell.mannlib.vitro.webapp.freemarker.loader.FreemarkerTemplateLoader.PathPieces;
import edu.cornell.mannlib.vitro.webapp.freemarker.loader.FreemarkerTemplateLoader.PathPiecesFileVisitor;
/**
* TODO
*/
public class FreemarkerTemplateLoaderTest {
private PathPiecesFileVisitor visitor;
private String[] paths;
// ----------------------------------------------------------------------
// PathPieces tests
// ----------------------------------------------------------------------
@Test
public void ppLanguageRegionExtension() {
assertPathPieces("this_en_US.ftl", "this", "_en", "_US", ".ftl");
}
@Test
public void ppLanguageRegion() {
assertPathPieces("this_en_US", "this", "_en", "_US", "");
}
@Test
public void ppLanguageExtension() {
assertPathPieces("this_en.ftl", "this", "_en", "", ".ftl");
}
@Test
public void ppLanguage() {
assertPathPieces("this_en", "this", "_en", "", "");
}
@Test
public void ppDefaultExtension() {
assertPathPieces("this.ftl", "this", "", "", ".ftl");
}
@Test
public void ppDefault() {
assertPathPieces("this", "this", "", "", "");
}
@Test
public void ppExtraUnderscoreExtension() {
assertPathPieces("woo_hoo_en_US.ftl", "woo_hoo", "_en", "_US", ".ftl");
}
@Test
public void ppExtraUnderscore() {
assertPathPieces("woo_hoo_en_US", "woo_hoo", "_en", "_US", "");
}
// ----------------------------------------------------------------------
// Specific function tests
// ----------------------------------------------------------------------
@Test
public void baseAndExtensionMatch() {
paths("match-me.ftl");
assertMatches("match-me.ftl", 1, "match-me.ftl");
}
@Test
public void baseAndExtensionDontMatch() {
paths("match-me.ftl");
assertMatches("fail.ftl", 0, null);
assertMatches("match-me", 0, null);
assertMatches("match-me.FTL", 0, null);
}
@Test
public void matchRegardlessOfDepth() {
paths("short-path.ftl", "long/long-path.ftl");
assertMatches("long/short-path.ftl", 1, "short-path.ftl");
assertMatches("long-path.ftl", 1, "long/long-path.ftl");
}
@Test
public void preferShorterPath() {
paths("shorter-is-better", "long/shorter-is-better");
assertMatches("shorter-is-better", 2, "shorter-is-better");
}
@Test
public void preferShorterPathToExactPath() {
paths("shorter-is-better", "long/shorter-is-better");
assertMatches("long/shorter-is-better", 2, "shorter-is-better");
}
@Test
public void languageAndRegionMustMatchExactly() {
paths("this_es_MX.ftl", "this_es_ES.ftl", "this_es.ftl");
assertMatches("this_es_ES.ftl", 1, "this_es_ES.ftl");
}
@Test
public void languageAndRegionNoMatch() {
paths("this_es_MX.ftl", "this_es_ES.ftl", "this_es.ftl");
assertMatches("this_es_GO.ftl", 0, null);
}
@Test
public void languagePrefersExactMatch() {
paths("this_es_MX.ftl", "this_es.ftl", "this_es_ES.ftl");
assertMatches("this_es.ftl", 3, "this_es.ftl");
}
@Test
public void languageAcceptsApproximateMatch() {
paths("this_es_MX.ftl");
assertMatches("this_es.ftl", 1, "this_es_MX.ftl");
}
@Test
public void languagePrefersApproximateAlphabetical() {
paths("this_es_MX.ftl", "this_es_ES.ftl");
assertMatches("this_es.ftl", 2, "this_es_ES.ftl");
}
@Test
public void defaultPrefersExactMatch() {
paths("this_fr.ftl", "this.ftl", "this_fr_BE.ftl");
assertMatches("this.ftl", 3, "this.ftl");
}
@Test
public void defaultPrefersDefaultRegion() {
paths("this_fr_BE.ftl", "this_fr.ftl", "this_fr_CA.ftl");
assertMatches("this.ftl", 3, "this_fr.ftl");
}
@Test
public void defaultPrefersLanguageAlphabetical() {
paths("this_es.ftl", "this_fr.ftl");
assertMatches("this.ftl", 2, "this_es.ftl");
}
@Test
public void defaultPrefersRegionAlphabetical() {
paths("this_fr_BE.ftl", "this_fr_CA.ftl");
assertMatches("this.ftl", 2, "this_fr_BE.ftl");
}
// ----------------------------------------------------------------------
// Freemarker simulation tests
// ----------------------------------------------------------------------
public static final String[] FREEMARKER_TEST_PATHS = {
"long/this_fr_BE.ftl", "language_fr.ftl", "default.ftl",
"language-approx_en_US.ftl" };
@Test
public void freemarkerLangAndRegionExact() {
paths = FREEMARKER_TEST_PATHS;
assertFM("this_fr_BE.ftl", 1, "long/this_fr_BE.ftl");
}
@Test
public void freemarkerLangAndRegionMatchLang() {
paths = FREEMARKER_TEST_PATHS;
assertFM("language_fr_CA.ftl", 2, "language_fr.ftl");
}
@Test
public void freemarkerLangAndRegionMatchDefault() {
paths = FREEMARKER_TEST_PATHS;
assertFM("default_es_ES.ftl", 3, "default.ftl");
}
@Test
public void freemarkerLangAndRegionNoMatch() {
paths = FREEMARKER_TEST_PATHS;
assertFM("bogus_en_US.ftl", 3, null);
}
@Test
public void freemarkerLangExact() {
paths = FREEMARKER_TEST_PATHS;
assertFM("language_fr.ftl", 1, "language_fr.ftl");
}
@Test
public void freemarkerLangMatchLangAndRegion() {
paths = FREEMARKER_TEST_PATHS;
assertFM("language-approx_en.ftl", 1, "language-approx_en_US.ftl");
}
@Test
public void freemarkerLangMatchDefault() {
paths = FREEMARKER_TEST_PATHS;
assertFM("default_en.ftl", 2, "default.ftl");
}
@Test
public void freemarkerLangNoMatch() {
paths = FREEMARKER_TEST_PATHS;
assertFM("bogus_it.ftl", 2, null);
}
@Test
public void freemarkerDefaultExact() {
paths = FREEMARKER_TEST_PATHS;
assertFM("default.ftl", 1, "default.ftl");
}
@Test
public void freemarkerDefaultMatchLang() {
paths = FREEMARKER_TEST_PATHS;
assertFM("language.ftl", 1, "language_fr.ftl");
}
@Test
public void freemarkerDefaultMatchLangAndRegion() {
paths = FREEMARKER_TEST_PATHS;
assertFM("this.ftl", 1, "long/this_fr_BE.ftl");
}
@Test
public void freemarkerDefaultNoMatch() {
paths = FREEMARKER_TEST_PATHS;
assertFM("bogus.ftl", 1, null);
}
// ----------------------------------------------------------------------
// Helper methods
// ----------------------------------------------------------------------
private void paths(String... p) {
this.paths = p;
}
private void assertPathPieces(String path, String base, String language,
String region, String extension) {
PathPieces pp = new PathPieces(path);
String[] expected = new String[] { base, language, region, extension };
String[] actual = new String[] { pp.base, pp.language, pp.region,
pp.extension };
assertEquals("pieces", Arrays.asList(expected), Arrays.asList(actual));
}
/**
* @param searchTerm
* template we are looking for
* @param expectedHowMany
* How many matches do we expect?
* @param expectedBestFit
* What should the best match turn out to be?
* @throws IOException
*/
private void assertMatches(String searchTerm, int expectedHowMany,
String expectedBestFit) {
SortedSet<PathPieces> matches = runTheVisitor(searchTerm);
int actualHowMany = matches.size();
String actualBestFit = matches.isEmpty() ? null : matches.last().path
.toString();
if (expectedHowMany != actualHowMany) {
fail("How many results: expected " + expectedHowMany
+ ", but was " + actualHowMany + ": " + matches);
}
if (!StringUtils.equals(expectedBestFit, actualBestFit)) {
fail("Best result: expected '" + expectedBestFit + "', but was '"
+ actualBestFit + "': " + matches);
}
}
/**
* Try for exact match, then pare down if needed, just like Freemarker
* would.
*/
private void assertFM(String searchTerm, int expectedNumberOfTries,
String expectedBestFit) {
PathPieces stPp = new PathPieces(searchTerm);
int actualNumberOfTries = 0;
String actualBestFit = null;
if (StringUtils.isNotBlank(stPp.region)) {
actualNumberOfTries++;
SortedSet<PathPieces> matches = runTheVisitor(stPp.base
+ stPp.language + stPp.region + stPp.extension);
if (!matches.isEmpty()) {
actualBestFit = matches.last().path.toString();
}
}
if (actualBestFit == null && StringUtils.isNotBlank(stPp.language)) {
actualNumberOfTries++;
SortedSet<PathPieces> matches = runTheVisitor(stPp.base
+ stPp.language + stPp.extension);
if (!matches.isEmpty()) {
actualBestFit = matches.last().path.toString();
}
}
if (actualBestFit == null) {
actualNumberOfTries++;
SortedSet<PathPieces> matches = runTheVisitor(stPp.base
+ stPp.extension);
if (!matches.isEmpty()) {
actualBestFit = matches.last().path.toString();
}
}
assertEquals("How many tries", expectedNumberOfTries,
actualNumberOfTries);
assertEquals("best fit", expectedBestFit, actualBestFit);
}
private SortedSet<PathPieces> runTheVisitor(String searchTerm) {
try {
visitor = new PathPiecesFileVisitorStub(new PathPieces(searchTerm));
for (String p : this.paths) {
visitor.visitFile(Paths.get(p), null);
}
} catch (IOException e) {
fail("Failed: " + e);
}
return visitor.getMatches();
}
// ----------------------------------------------------------------------
// Helper classes
// ----------------------------------------------------------------------
/**
* We want to test the PathPiecesFileVisitor, but we can't have it checking
* to see whether the files actually exist.
*/
private static class PathPiecesFileVisitorStub extends
PathPiecesFileVisitor {
public PathPiecesFileVisitorStub(PathPieces searchTerm) {
super(searchTerm);
}
@Override
public boolean fileQualifies(Path path) {
return true;
}
}
}

View file

@ -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

View file

@ -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>

View file

@ -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>

View file

@ -0,0 +1,81 @@
/* $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("developerEnabled").onchange = updateDisabledFields
document.getElementById("developerLoggingRDFServiceEnable").onchange = updateDisabledFields
}
function updateDisabledFields() {
var developerEnabled = document.getElementById("developerEnabled").checked;
document.getElementById("developerDefeatFreemarkerCache").disabled = !developerEnabled;
document.getElementById("developerInsertFreemarkerDelimiters").disabled = !developerEnabled;
document.getElementById("developerI18nDefeatCache").disabled = !developerEnabled;
document.getElementById("developerI18nLogStringRequests").disabled = !developerEnabled;
document.getElementById("developerLoggingRDFServiceEnable").disabled = !developerEnabled;
var rdfServiceEnabled = developerEnabled && document.getElementById("developerLoggingRDFServiceEnable").checked;
document.getElementById("developerLoggingRDFServiceStackTrace").disabled = !rdfServiceEnabled;
document.getElementById("developerLoggingRDFServiceRestriction").disabled = !rdfServiceEnabled;
}
function collectFormData() {
var data = new Object();
getCheckbox("developerEnabled", data);
getCheckbox("developerDefeatFreemarkerCache", data);
getCheckbox("developerInsertFreemarkerDelimiters", data);
getCheckbox("developerI18nDefeatCache", data);
getCheckbox("developerI18nLogStringRequests", data);
getCheckbox("developerLoggingRDFServiceEnable", data);
getCheckbox("developerLoggingRDFServiceStackTrace", data);
getText("developerLoggingRDFServiceRestriction", 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({});
});

View file

@ -6,8 +6,9 @@
<#if (rangeOptions?keys?size > 0)> <#if (rangeOptions?keys?size > 0)>
<#assign rangeOptionsExist = true/> <#assign rangeOptionsExist = true/>
</#if> </#if>
<#assign rangeUri = editConfiguration.objectPredicateProperty.rangeVClassURI!"" />
<#assign formTitle = editConfiguration.formTitle /> <#assign formTitle = editConfiguration.formTitle />
<#if editConfiguration.objectPredicateProperty.rangeVClassURI?contains("IAO_0000030")> <#if rangeUri?contains("IAO_0000030")>
<#assign formTitle = "${i18n().select_an_existing_document}" + " ${i18n().for} " + editConfiguration.subjectName/> <#assign formTitle = "${i18n().select_an_existing_document}" + " ${i18n().for} " + editConfiguration.subjectName/>
</#if> </#if>
<h2>${formTitle}</h2> <h2>${formTitle}</h2>

View file

@ -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>')}

View file

@ -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: #f7dd8a;
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.developerEnabled>
<#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 "developerEnabled" />
Enable developer mode
</label>
<div class="container">
Freemarker templates
<label>
<@showCheckbox "developerDefeatFreemarkerCache" />
Defeat the template cache
</label>
<label>
<@showCheckbox "developerInsertFreemarkerDelimiters" />
Insert HTML comments at start and end of templates
</label>
</div>
<div class="container">
SPARQL Queries
<label>
<@showCheckbox "developerLoggingRDFServiceEnable" />
Log each query
</label>
<label>
<@showCheckbox "developerLoggingRDFServiceStackTrace" />
Add stack trace
</label>
<label>
Restrict by calling stack
<@showTextbox "developerLoggingRDFServiceRestriction" />
</label>
</div>
<div class="container">
Language support
<label>
<@showCheckbox "developerI18nDefeatCache" />
Defeat the cache of language property files
</label>
<label>
<@showCheckbox "developerI18nLogStringRequests" />
Log the retrieval of language strings
</label>
</div>
<input type="button" id="developerPanelSaveButton" value="Save Settings" name="foo" />
</form>
</div>
</div>
</#if>

View file

@ -6,11 +6,11 @@
What was the right way to do this? What was the right way to do this?
--> -->
<#-- This is included by identity.ftl --> <#-- This is included by identity.ftl -->
<#if selectLocale??> <#if selectLocale??>
<#list selectLocale.locales as locale> <#list selectLocale.locales as locale>
<li> <li>
<a href="${selectLocale.selectLocaleUrl}?selection=${locale.code}" title="${i18n().select_locale} -- ${locale.label}"> <a href="${selectLocale.selectLocaleUrl}?selection=${locale.code}" title="${i18n().select_locale} -- ${locale.label}" <#if locale.selected>style="padding-bottom:1px;border-bottom: 1px solid #ccdfe6"</#if>>
<img src="${locale.imageUrl}" height="15" style="vertical-align:middle" alt="${locale.label}"/> <img src="${locale.imageUrl}" height="15" style="vertical-align:middle" alt="${locale.label}"/>
</a> </a>
</li> </li>

View file

@ -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>