Merge branch 'maint-rel-1.6' of https://github.com/vivo-project/Vitro into maint-rel-1.6
This commit is contained in:
commit
319c495617
37 changed files with 1584 additions and 524 deletions
|
@ -76,6 +76,8 @@ public class SimplePermission extends Permission {
|
|||
NAMESPACE + "UseAdvancedDataToolsPages");
|
||||
public static final SimplePermission USE_SPARQL_QUERY_PAGE = new SimplePermission(
|
||||
NAMESPACE + "UseSparqlQueryPage");
|
||||
public static final SimplePermission ENABLE_DEVELOPER_PANEL = new SimplePermission(
|
||||
NAMESPACE + "EnableDeveloperPanel");
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
|
|
@ -57,7 +57,7 @@ public class Classes2ClassesOperationController extends BaseEditController {
|
|||
return;
|
||||
}
|
||||
|
||||
VClassDao vcDao = request.getUnfilteredAssertionsWebappDaoFactory().getVClassDao();
|
||||
VClassDao vcDao = request.getLanguageNeutralWebappDaoFactory().getVClassDao();
|
||||
|
||||
String modeStr = request.getParameter("opMode");
|
||||
modeStr = (modeStr == null) ? "" : modeStr;
|
||||
|
|
|
@ -38,6 +38,7 @@ public class DelimitingTemplateLoader implements TemplateLoader {
|
|||
@Override
|
||||
public Object findTemplateSource(String name) throws IOException {
|
||||
Object innerTS = innerLoader.findTemplateSource(name);
|
||||
log.debug("template source for '" + name + "' is '" + innerTS + "'");
|
||||
if (innerTS == null) {
|
||||
return null;
|
||||
} else {
|
||||
|
|
|
@ -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 {
|
||||
}
|
||||
|
||||
}
|
|
@ -1048,11 +1048,20 @@ public class VClassDaoJena extends JenaBaseDao implements VClassDao {
|
|||
try {
|
||||
OntResource subclass = getOntClass(ontModel,c2c.getSubclassURI());
|
||||
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 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()) {
|
||||
Model[] changeSet = getSmartRemoval(subclass, getOntModel());
|
||||
removal.add(changeSet[0]);
|
||||
|
|
|
@ -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() {
|
||||
return Boolean.valueOf(props.getProperty("RDFService.languageFilter",
|
||||
"true"));
|
||||
"false"));
|
||||
}
|
||||
|
||||
private RDFService addLanguageAwareness(HttpServletRequest req,
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
package edu.cornell.mannlib.vitro.webapp.freemarker.config;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
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.LogFactory;
|
||||
|
||||
import edu.cornell.mannlib.vitro.webapp.config.ConfigurationProperties;
|
||||
import edu.cornell.mannlib.vitro.webapp.config.RevisionInfoBean;
|
||||
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.FlatteningTemplateLoader;
|
||||
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.freemarker.loader.FreemarkerTemplateLoader;
|
||||
import edu.cornell.mannlib.vitro.webapp.i18n.freemarker.I18nMethodModel;
|
||||
import edu.cornell.mannlib.vitro.webapp.startup.StartupStatus;
|
||||
import edu.cornell.mannlib.vitro.webapp.utils.developer.DeveloperSettings;
|
||||
import edu.cornell.mannlib.vitro.webapp.utils.developer.DeveloperSettings.Keys;
|
||||
import edu.cornell.mannlib.vitro.webapp.web.directives.IndividualShortViewDirective;
|
||||
import edu.cornell.mannlib.vitro.webapp.web.directives.UrlDirective;
|
||||
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.IndividualProfileUrlMethod;
|
||||
import freemarker.cache.ClassTemplateLoader;
|
||||
import freemarker.cache.FileTemplateLoader;
|
||||
import freemarker.cache.MultiTemplateLoader;
|
||||
import freemarker.cache.TemplateLoader;
|
||||
import freemarker.ext.beans.BeansWrapper;
|
||||
|
@ -55,18 +54,17 @@ import freemarker.template.TemplateModelException;
|
|||
* own locale, etc.
|
||||
*
|
||||
* 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
|
||||
* TemplateLoader). Store the request info to the ThreadLocal.
|
||||
* cache is still valid, whether the theme has changed (needs a new
|
||||
* TemplateLoader), and whether the DeveloperSettings have changed (might need a
|
||||
* new TemplateLoader). Store the request info to the ThreadLocal.
|
||||
*/
|
||||
public abstract class FreemarkerConfiguration {
|
||||
private static final Log log = LogFactory
|
||||
.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 String previousThemeDir;
|
||||
private static volatile Map<String, Object> previousSettingsMap;
|
||||
|
||||
public static Configuration getConfig(HttpServletRequest req) {
|
||||
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) {
|
||||
ConfigurationProperties props = ConfigurationProperties.getBean(req);
|
||||
|
||||
// If the developer doesn't want the cache, it's invalid.
|
||||
if (Boolean.valueOf(props.getProperty(PROPERTY_DEFEAT_CACHE))) {
|
||||
DeveloperSettings settings = DeveloperSettings.getBean(req);
|
||||
if (settings.getBoolean(Keys.DEFEAT_FREEMARKER_CACHE)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -113,7 +109,8 @@ public abstract class FreemarkerConfiguration {
|
|||
private static void keepTemplateLoaderCurrentWithThemeDirectory(
|
||||
HttpServletRequest req) {
|
||||
String themeDir = getThemeDirectory(req);
|
||||
if (hasThemeDirectoryChanged(themeDir)) {
|
||||
if (hasThemeDirectoryChanged(themeDir)
|
||||
|| haveDeveloperSettingsChanged(req)) {
|
||||
TemplateLoader tl = createTemplateLoader(req, themeDir);
|
||||
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,
|
||||
String themeDir) {
|
||||
ServletContext ctx = req.getSession().getServletContext();
|
||||
ConfigurationProperties props = ConfigurationProperties.getBean(ctx);
|
||||
|
||||
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";
|
||||
File themeTemplateDir = new File(themeTemplatePath);
|
||||
// A theme need not contain a template directory.
|
||||
if (themeTemplateDir.exists()) {
|
||||
try {
|
||||
FileTemplateLoader themeFtl = new FileTemplateLoader(
|
||||
themeTemplateDir);
|
||||
loaders.add(themeFtl);
|
||||
} catch (IOException e) {
|
||||
log.error("Error creating theme template loader", e);
|
||||
}
|
||||
loaders.add(new FreemarkerTemplateLoader(themeTemplateDir));
|
||||
}
|
||||
|
||||
// Vitro template loader
|
||||
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?
|
||||
loaders.add(new ClassTemplateLoader(FreemarkerConfiguration.class, ""));
|
||||
|
||||
|
||||
TemplateLoader[] loaderArray = loaders
|
||||
.toArray(new TemplateLoader[loaders.size()]);
|
||||
MultiTemplateLoader mtl = new MultiTemplateLoader(loaderArray);
|
||||
TemplateLoader tl = new MultiTemplateLoader(loaderArray);
|
||||
|
||||
// If requested, add delimiters to the templates.
|
||||
if (Boolean.valueOf(props.getProperty(PROPERTY_INSERT_DELIMITERS))) {
|
||||
return new DelimitingTemplateLoader(mtl);
|
||||
} else {
|
||||
return mtl;
|
||||
DeveloperSettings settings = DeveloperSettings.getBean(req);
|
||||
if (settings.getBoolean(Keys.INSERT_FREEMARKER_DELIMITERS)) {
|
||||
tl = new DelimitingTemplateLoader(tl);
|
||||
}
|
||||
|
||||
return tl;
|
||||
}
|
||||
|
||||
private static void setThreadLocalsForRequest(HttpServletRequest req) {
|
||||
|
|
|
@ -287,6 +287,7 @@ public class FreemarkerConfigurationImpl extends Configuration {
|
|||
urls.put("images", UrlBuilder.getUrl("/images"));
|
||||
urls.put("theme", UrlBuilder.getUrl(themeDir));
|
||||
urls.put("index", UrlBuilder.getUrl("/browse"));
|
||||
urls.put("developerAjax", UrlBuilder.getUrl("/admin/developerAjax"));
|
||||
|
||||
return urls;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -15,8 +15,9 @@ 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;
|
||||
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,
|
||||
|
@ -31,7 +32,6 @@ public class I18n {
|
|||
private static final Log log = LogFactory.getLog(I18n.class);
|
||||
|
||||
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
|
||||
|
@ -103,6 +103,7 @@ public class I18n {
|
|||
protected I18nBundle getBundle(String bundleName, HttpServletRequest req) {
|
||||
log.debug("Getting bundle '" + bundleName + "'");
|
||||
|
||||
I18nLogger i18nLogger = new I18nLogger(req);
|
||||
try {
|
||||
checkDevelopmentMode(req);
|
||||
checkForChangeInThemeDirectory(req);
|
||||
|
@ -113,13 +114,13 @@ public class I18n {
|
|||
ResourceBundle.Control control = new ThemeBasedControl(ctx, dir);
|
||||
ResourceBundle rb = ResourceBundle.getBundle(bundleName,
|
||||
req.getLocale(), control);
|
||||
return new I18nBundle(bundleName, rb);
|
||||
return new I18nBundle(bundleName, rb, i18nLogger);
|
||||
} catch (MissingResourceException e) {
|
||||
log.warn("Didn't find text bundle '" + bundleName + "'");
|
||||
return I18nBundle.emptyBundle(bundleName);
|
||||
return I18nBundle.emptyBundle(bundleName, i18nLogger);
|
||||
} catch (Exception 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.
|
||||
*/
|
||||
private void checkDevelopmentMode(HttpServletRequest req) {
|
||||
ConfigurationProperties bean = ConfigurationProperties.getBean(req);
|
||||
|
||||
String flag = bean
|
||||
.getProperty(PROPERTY_DEVELOPER_DEFEAT_CACHE, "false");
|
||||
if (Boolean.valueOf(flag.trim())) {
|
||||
if (DeveloperSettings.getBean(req).getBoolean(Keys.I18N_DEFEAT_CACHE) ) {
|
||||
log.debug("In development mode - clearing the cache.");
|
||||
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_KEY_NOT_FOUND = "Text bundle ''{0}'' has no text for ''{1}''";
|
||||
|
||||
public static I18nBundle emptyBundle(String bundleName) {
|
||||
return new I18nBundle(bundleName);
|
||||
public static I18nBundle emptyBundle(String bundleName,
|
||||
I18nLogger i18nLogger) {
|
||||
return new I18nBundle(bundleName, i18nLogger);
|
||||
}
|
||||
|
||||
private final String bundleName;
|
||||
private final ResourceBundle resources;
|
||||
private final String notFoundMessage;
|
||||
private final I18nLogger i18nLogger;
|
||||
|
||||
private I18nBundle(String bundleName) {
|
||||
this(bundleName, new EmptyResourceBundle(), MESSAGE_BUNDLE_NOT_FOUND);
|
||||
private I18nBundle(String bundleName, I18nLogger i18nLogger) {
|
||||
this(bundleName, new EmptyResourceBundle(), MESSAGE_BUNDLE_NOT_FOUND,
|
||||
i18nLogger);
|
||||
}
|
||||
|
||||
public I18nBundle(String bundleName, ResourceBundle resources) {
|
||||
this(bundleName, resources, MESSAGE_KEY_NOT_FOUND);
|
||||
public I18nBundle(String bundleName, ResourceBundle resources,
|
||||
I18nLogger i18nLogger) {
|
||||
this(bundleName, resources, MESSAGE_KEY_NOT_FOUND, i18nLogger);
|
||||
}
|
||||
|
||||
private I18nBundle(String bundleName, ResourceBundle resources,
|
||||
String notFoundMessage) {
|
||||
String notFoundMessage, I18nLogger i18nLogger) {
|
||||
if (bundleName == null) {
|
||||
throw new IllegalArgumentException("bundleName may not be null");
|
||||
}
|
||||
|
@ -57,22 +61,27 @@ public class I18nBundle {
|
|||
this.bundleName = bundleName;
|
||||
this.resources = resources;
|
||||
this.notFoundMessage = notFoundMessage;
|
||||
this.i18nLogger = i18nLogger;
|
||||
}
|
||||
|
||||
public String text(String key, Object... parameters) {
|
||||
|
||||
String textString;
|
||||
if (resources.containsKey(key)) {
|
||||
textString = resources.getString(key);
|
||||
log.debug("In '" + bundleName + "', " + key + "='" + textString
|
||||
+ "')");
|
||||
return formatString(textString, parameters);
|
||||
} else {
|
||||
String message = MessageFormat.format(notFoundMessage, bundleName,
|
||||
key);
|
||||
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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -207,6 +207,14 @@ public class KnowledgeBaseUpdater {
|
|||
StmtIterator sit = anonModel.listStatements();
|
||||
while (sit.hasNext()) {
|
||||
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();
|
||||
while(graphIt.hasNext()) {
|
||||
String graph = graphIt.next();
|
||||
|
@ -223,8 +231,9 @@ public class KnowledgeBaseUpdater {
|
|||
//log.info("removed " + anonModel.size() + " statements from SPARQL CONSTRUCTs");
|
||||
} else {
|
||||
Model writeModel = dataset.getNamedModel(JenaDataSourceSetupBase.JENA_DB_MODEL);
|
||||
Model dedupeModel = dataset.getDefaultModel();
|
||||
Model additions = jiu.renameBNodes(
|
||||
anonModel, settings.getDefaultNamespace() + "n", writeModel);
|
||||
anonModel, settings.getDefaultNamespace() + "n", dedupeModel);
|
||||
Model actualAdditions = ModelFactory.createDefaultModel();
|
||||
StmtIterator stmtIt = additions.listStatements();
|
||||
while (stmtIt.hasNext()) {
|
||||
|
|
|
@ -217,6 +217,9 @@ public abstract class RDFServiceJena extends RDFServiceImpl implements RDFServic
|
|||
private List<Statement> sort(List<Statement> stmts) {
|
||||
List<Statement> output = new ArrayList<Statement>();
|
||||
int originalSize = stmts.size();
|
||||
if(originalSize == 1) {
|
||||
return stmts;
|
||||
}
|
||||
List <Statement> remaining = stmts;
|
||||
ConcurrentLinkedQueue<Resource> subjQueue = new ConcurrentLinkedQueue<Resource>();
|
||||
for(Statement stmt : remaining) {
|
||||
|
|
|
@ -15,7 +15,8 @@ import org.apache.commons.lang.StringUtils;
|
|||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import edu.cornell.mannlib.vitro.webapp.config.ConfigurationProperties;
|
||||
import edu.cornell.mannlib.vitro.webapp.utils.developer.DeveloperSettings;
|
||||
import edu.cornell.mannlib.vitro.webapp.utils.developer.DeveloperSettings.Keys;
|
||||
|
||||
/**
|
||||
* Writes the log message for the LoggingRDFService.
|
||||
|
@ -41,10 +42,6 @@ import edu.cornell.mannlib.vitro.webapp.config.ConfigurationProperties;
|
|||
public class RDFServiceLogger implements AutoCloseable {
|
||||
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 Object[] args;
|
||||
|
||||
|
@ -72,18 +69,21 @@ public class RDFServiceLogger implements AutoCloseable {
|
|||
}
|
||||
|
||||
private void getProperties() {
|
||||
ConfigurationProperties props = ConfigurationProperties.getBean(ctx);
|
||||
isEnabled = Boolean.valueOf(props.getProperty(PROPERTY_ENABLED));
|
||||
traceRequested = Boolean.valueOf(props
|
||||
.getProperty(PROPERTY_STACK_TRACE));
|
||||
DeveloperSettings settings = DeveloperSettings.getBean(ctx);
|
||||
isEnabled = settings.getBoolean(Keys.LOGGING_RDF_ENABLE);
|
||||
traceRequested = settings.getBoolean(Keys.LOGGING_RDF_STACK_TRACE);
|
||||
|
||||
String restrictionString = props.getProperty(PROPERTY_RESTRICTION);
|
||||
if (StringUtils.isNotBlank(restrictionString)) {
|
||||
String restrictionString = settings
|
||||
.getString(Keys.LOGGING_RDF_RESTRICTION);
|
||||
if (StringUtils.isBlank(restrictionString)) {
|
||||
restriction = null;
|
||||
} else {
|
||||
try {
|
||||
restriction = Pattern.compile(restrictionString);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to compile the pattern for "
|
||||
+ PROPERTY_RESTRICTION + " = " + restriction + " " + e);
|
||||
+ Keys.LOGGING_RDF_RESTRICTION + " = " + restriction
|
||||
+ " " + e);
|
||||
isEnabled = false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -744,6 +744,8 @@ public class RDFServiceSparql extends RDFServiceImpl implements RDFService {
|
|||
private List<Statement> sort(List<Statement> stmts) {
|
||||
List<Statement> output = new ArrayList<Statement>();
|
||||
int originalSize = stmts.size();
|
||||
if (originalSize == 1)
|
||||
return stmts;
|
||||
List <Statement> remaining = stmts;
|
||||
ConcurrentLinkedQueue<com.hp.hpl.jena.rdf.model.Resource> subjQueue =
|
||||
new ConcurrentLinkedQueue<com.hp.hpl.jena.rdf.model.Resource>();
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
package edu.cornell.mannlib.vitro.webapp.reasoner;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
|
@ -295,14 +296,35 @@ public class ABoxRecomputer {
|
|||
*/
|
||||
protected Collection<String> getAllIndividualURIs() {
|
||||
|
||||
String queryString = "SELECT DISTINCT ?s WHERE { GRAPH ?g { ?s a ?type } " +
|
||||
" FILTER (!bound(?g) || !regex(str(?g),\"tbox\")) } ORDER BY ?s";
|
||||
return getIndividualURIs(queryString);
|
||||
HashSet<String> individualURIs = new HashSet<String>();
|
||||
|
||||
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) {
|
||||
|
||||
Set<String> individuals = new HashSet<String>();
|
||||
protected void getIndividualURIs(String queryString, Set<String> individuals) {
|
||||
|
||||
int batchSize = 50000;
|
||||
int offset = 0;
|
||||
|
@ -342,7 +364,6 @@ public class ABoxRecomputer {
|
|||
offset += batchSize;
|
||||
}
|
||||
|
||||
return individuals;
|
||||
}
|
||||
|
||||
protected void addedABoxTypeAssertion(Resource individual, Model inferenceModel, HashSet<String> unknownTypes) {
|
||||
|
@ -410,6 +431,11 @@ public class ABoxRecomputer {
|
|||
while (iter.hasNext()) {
|
||||
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);
|
||||
try {
|
||||
inferenceModel.remove(stmt);
|
||||
|
|
|
@ -187,8 +187,7 @@ public class FileGraphSetup implements ServletContextListener {
|
|||
} else {
|
||||
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);
|
||||
|
|
|
@ -221,7 +221,7 @@ public class JenaDataSourceSetupBase extends JenaBaseDaoCon {
|
|||
int[] maxActiveAndIdle = getMaxActiveAndIdle(ctx);
|
||||
cpds.setMaxPoolSize(maxActiveAndIdle[0]);
|
||||
cpds.setMinPoolSize(maxActiveAndIdle[1]);
|
||||
cpds.setMaxIdleTime(3600); // ms
|
||||
cpds.setMaxIdleTime(43200); // s
|
||||
cpds.setMaxIdleTimeExcessConnections(300);
|
||||
cpds.setAcquireIncrement(5);
|
||||
cpds.setNumHelperThreads(6);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue