diff --git a/api/pom.xml b/api/pom.xml
index 83af0ba89..7c0fa9a1e 100644
--- a/api/pom.xml
+++ b/api/pom.xml
@@ -7,13 +7,13 @@
org.vivoweb
vitro-api
- 1.11.2-SNAPSHOT
+ 1.12.0-SNAPSHOT
jar
org.vivoweb
vitro-project
- 1.11.2-SNAPSHOT
+ 1.12.0-SNAPSHOT
..
@@ -66,7 +66,7 @@
org.vivoweb
vitro-dependencies
- 1.11.2-SNAPSHOT
+ 1.12.0-SNAPSHOT
pom
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/application/ApplicationSetup.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/application/ApplicationSetup.java
index 5b0ecace9..a8fd8aa2f 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/application/ApplicationSetup.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/application/ApplicationSetup.java
@@ -24,7 +24,6 @@ import edu.cornell.mannlib.vitro.webapp.utils.jena.criticalsection.LockableModel
*/
public class ApplicationSetup implements ServletContextListener {
private static final String APPLICATION_SETUP_PATH = "config/applicationSetup.n3";
- private static final String APPLICATION_SETUP_DEFAULT_PATH = "config/default.applicationSetup.n3";
private ServletContext ctx;
private StartupStatus ss;
@@ -46,8 +45,6 @@ public class ApplicationSetup implements ServletContextListener {
this.vitroHomeDir = VitroHomeDirectory.find(ctx);
ss.info(this, vitroHomeDir.getDiscoveryMessage());
- this.vitroHomeDir.populate();
-
locateApplicationConfigFile();
loadApplicationConfigFile();
createConfigurationBeanLoader();
@@ -66,19 +63,11 @@ public class ApplicationSetup implements ServletContextListener {
private void locateApplicationConfigFile() {
Path path = this.vitroHomeDir.getPath().resolve(APPLICATION_SETUP_PATH);
- if (!Files.exists(path) || !Files.isReadable(path)) {
- path = this.vitroHomeDir.getPath().resolve(APPLICATION_SETUP_DEFAULT_PATH);
- }
-
if (!Files.exists(path)) {
- throw new IllegalStateException("Neither '" + APPLICATION_SETUP_PATH + "' nor '" +
- APPLICATION_SETUP_DEFAULT_PATH + "' were found in " +
- this.vitroHomeDir.getPath());
+ throw new IllegalStateException("'" + path + "' does not exist.");
}
if (!Files.isReadable(path)) {
- throw new IllegalStateException("No readable '" + APPLICATION_SETUP_PATH + "' nor '" +
- APPLICATION_SETUP_DEFAULT_PATH + "' files were found in " +
- this.vitroHomeDir.getPath());
+ throw new IllegalStateException("Can't read '" + path + "'");
}
this.configFile = path;
}
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/application/VitroHomeDirectory.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/application/VitroHomeDirectory.java
index a5269e499..b55f9d87e 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/application/VitroHomeDirectory.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/application/VitroHomeDirectory.java
@@ -4,46 +4,25 @@ package edu.cornell.mannlib.vitro.webapp.application;
import static edu.cornell.mannlib.vitro.webapp.application.BuildProperties.WEBAPP_PATH_BUILD_PROPERTIES;
-import java.io.ByteArrayInputStream;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStreamWriter;
-import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.regex.Pattern;
-import java.util.stream.Collectors;
+import javax.naming.InitialContext;
import javax.servlet.ServletContext;
-import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
-import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
-import org.apache.commons.io.FileUtils;
-import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-import edu.cornell.mannlib.vitro.webapp.config.ContextProperties;
-
/**
- * Encapsulates some of the info relating to and initializes the Vitro home directory.
+ * Encapsulates some of the info relating to the Vitro home directory.
*/
public class VitroHomeDirectory {
private static final Log log = LogFactory.getLog(VitroHomeDirectory.class);
- private static final String DIGEST_FILE_NAME = "digest.md5";
-
- private static final Pattern CHECKSUM_PATTERN = Pattern.compile("^[a-f0-9]{32} \\*.+$");
-
public static VitroHomeDirectory find(ServletContext ctx) {
HomeDirectoryFinder finder = new HomeDirectoryFinder(ctx);
return new VitroHomeDirectory(ctx, finder.getPath(),
@@ -73,219 +52,6 @@ public class VitroHomeDirectory {
return discoveryMessage;
}
- /**
- * Populates VIVO home directory with files required to run.
- *
- * NOTE: Will not overwrite any modified files on redeploy.
- */
- public void populate() {
- File vhdDir = getPath().toFile();
-
- if (!vhdDir.isDirectory() || vhdDir.list() == null) {
- throw new RuntimeException("Application home dir is not a directory! " + vhdDir);
- }
-
- Map digest = untar(vhdDir);
-
- writeDigest(digest);
- }
-
- /**
- * A non-destructive untar process that returns checksum digest of tarred files.
- *
- * Checksum digest can be manually created with the following command.
- *
- * `find /vivo/home -type f | cut -c3- | grep -E '^bin/|^config/|^rdf/' | xargs md5sum > /vivo/home/digest.md5`
- *
- * @param destination VIVO home directory
- * @return digest of each files checksum
- */
- private Map untar(File destination) {
- log.info("Syncing VIVO home at: " + destination.getPath());
-
- Map digest = new HashMap<>();
- Map storedDigest = loadDigest();
-
- TarArchiveEntry tarEntry;
- try (
- InputStream homeDirTar = getHomeDirTar();
- TarArchiveInputStream tarInput = new TarArchiveInputStream(homeDirTar);
- ) {
- while ((tarEntry = tarInput.getNextTarEntry()) != null) {
-
- // Use the example configurations
- String outFilename = tarEntry.getName().replace("example.", "");
- File outFile = new File(destination, outFilename);
-
- // Is the entry a directory?
- if (tarEntry.isDirectory()) {
- if (!outFile.exists()) {
- outFile.mkdirs();
- }
- } else {
- // Entry is a File
- boolean write = true;
-
- // reading bytes into memory to avoid having to unreliably reset stream
- byte[] bytes = IOUtils.toByteArray(tarInput);
- String newFileChecksum = checksum(bytes);
- digest.put(outFilename, newFileChecksum);
-
- // if file already exists and stored digest contains the file,
- // check to determine if it has changed
- if (outFile.exists() && storedDigest.containsKey(outFilename)) {
- String existingFileChecksum = checksum(outFile);
- // if file has not changed in home and is not the same as new file, overwrite
- write = storedDigest.get(outFilename).equals(existingFileChecksum)
- && !existingFileChecksum.equals(newFileChecksum);
- }
-
- if (write) {
- outFile.getParentFile().mkdirs();
- try (
- InputStream is = new ByteArrayInputStream(bytes);
- FileOutputStream fos = new FileOutputStream(outFile);
- ) {
- IOUtils.copy(is, fos);
- log.info(outFile.getAbsolutePath() + " source has changed and has not been "
- + "edited in home, updated file has been copied to home directory.");
- }
- } else {
- log.debug(outFile.getAbsolutePath() + " has been preserved.");
- }
- }
- }
- } catch (IOException | NoSuchAlgorithmException e) {
- throw new RuntimeException("Error creating home directory!", e);
- }
-
- return digest;
- }
-
- /**
- * Load checksum digest of VIVO home directory.
- *
- * @return checksum digest
- */
- private Map loadDigest() {
- File storedDigest = new File(getPath().toFile(), DIGEST_FILE_NAME);
- if (storedDigest.exists() && storedDigest.isFile()) {
- log.info("Reading VIVO home digest: " + storedDigest.getPath());
- try {
- return FileUtils
- .readLines(storedDigest, StandardCharsets.UTF_8)
- .stream()
- .filter(CHECKSUM_PATTERN.asPredicate())
- .map(this::split)
- .collect(Collectors.toMap(this::checksumFile, this::checksumValue));
- } catch (IOException e) {
- throw new RuntimeException("Error reading VIVO home checksum digest!", e);
- }
- }
- log.info("VIVO home digest not found: " + storedDigest.getPath());
-
- return new HashMap<>();
- }
-
- /**
- * Write VIVO home checksum digest following md5 format; ` *`.
- *
- * @param digest checksum digest to write
- */
- private void writeDigest(Map digest) {
- File storedDigest = new File(getPath().toFile(), DIGEST_FILE_NAME);
- try (
- FileOutputStream fos = new FileOutputStream(storedDigest);
- OutputStreamWriter osw = new OutputStreamWriter(fos);
- ) {
- for (Map.Entry entry : digest.entrySet()) {
- String filename = entry.getKey();
- String checksum = entry.getValue();
- osw.write(String.format("%s *%s\n", checksum, filename));
- }
- } catch (IOException e) {
- throw new RuntimeException("Error writing home directory checksum digest!", e);
- }
- log.info("VIVO home digest created: " + storedDigest.getPath());
- }
-
- /**
- * Split checksum.
- *
- * @param checksum checksum delimited by space and asterisks ` *`
- * @return split checksum
- */
- private String[] split(String checksum) {
- return checksum.split("\\s+");
- }
-
- /**
- * Get value from split checksum.
- *
- * @param checksum split checksum
- * @return checksum value
- */
- private String checksumValue(String[] checksum) {
- return checksum[0];
- }
-
- /**
- * Return file from split checksum.
- *
- * @param checksum split checksum
- * @return filename
- */
- private String checksumFile(String[] checksum) {
- return checksum[1].substring(1);
- }
-
- /**
- * Get md5 checksum from file.
- *
- * @param file file
- * @return md5 checksum as string
- * @throws IOException
- * @throws NoSuchAlgorithmException
- */
- private String checksum(File file) throws IOException, NoSuchAlgorithmException {
- return checksum(FileUtils.readFileToByteArray(file));
- }
-
- /**
- * Get md5 checksum from bytes.
- *
- * @param bytes bytes from file
- * @return md5 checksum as string
- * @throws NoSuchAlgorithmException
- */
- private String checksum(byte[] bytes) throws NoSuchAlgorithmException {
- MessageDigest md = MessageDigest.getInstance("MD5");
- md.update(bytes);
- // bytes to hex
- StringBuilder result = new StringBuilder();
- for (byte b : md.digest()) {
- result.append(String.format("%02x", b));
- }
-
- return result.toString();
- }
-
- /**
- * Get prepacked VIVO home tar file as input stream.
- *
- * @return input stream of VIVO home tar file
- */
- private InputStream getHomeDirTar() {
- String tarLocation = "/WEB-INF/resources/home-files/vivo-home.tar";
- InputStream tar = ctx.getResourceAsStream(tarLocation);
- if (tar == null) {
- log.error("Application home tar not found in: " + tarLocation);
- throw new RuntimeException("Application home tar not found in: " + tarLocation);
- }
-
- return tar;
- }
-
/**
* Find something that specifies the location of the Vitro home directory.
* Look in the JDNI environment, the system properties, and the
@@ -326,12 +92,23 @@ public class VitroHomeDirectory {
}
public void getVhdFromJndi() {
- String vhdPath = ContextProperties.findJndiProperty(VHD_JNDI_PATH);
- log.debug("'" + VHD_JNDI_PATH + "' as specified by JNDI: " + vhdPath);
- String message = String.format(
- "JNDI environment '%s' was set to '%s'",
- VHD_JNDI_PATH, vhdPath);
- foundLocations.add(new Found(Paths.get(vhdPath), message));
+ try {
+ String vhdPath = (String) new InitialContext()
+ .lookup(VHD_JNDI_PATH);
+ if (vhdPath == null) {
+ log.debug("Didn't find a JNDI value at '" + VHD_JNDI_PATH
+ + "'.");
+ } else {
+ log.debug("'" + VHD_JNDI_PATH + "' as specified by JNDI: "
+ + vhdPath);
+ String message = String.format(
+ "JNDI environment '%s' was set to '%s'",
+ VHD_JNDI_PATH, vhdPath);
+ foundLocations.add(new Found(Paths.get(vhdPath), message));
+ }
+ } catch (Exception e) {
+ log.debug("JNDI lookup failed. " + e);
+ }
}
private void getVhdFromSystemProperties() {
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/beans/UserAccount.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/beans/UserAccount.java
index 7b9d49792..7b5581885 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/beans/UserAccount.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/beans/UserAccount.java
@@ -7,17 +7,11 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
-import edu.cornell.mannlib.vitro.webapp.controller.authenticate.Authenticator;
+import org.apache.commons.lang3.RandomStringUtils;
/**
* Information about the account of a user. URI, email, password, etc.
*
- * The "password link expires hash" is just a string that is derived from the
- * value in the passwordLinkExpires field. It doesn't have to be a hash, and
- * there is no need for it to be cryptographic, but it seems embarrassing to
- * just send the value as a clear string. There is no real need for security
- * here, except that a brute force attack would allow someone to change the
- * password on an account that they know has a password change pending.
*/
public class UserAccount {
public static final int MIN_PASSWORD_LENGTH = 6;
@@ -52,6 +46,7 @@ public class UserAccount {
private String md5Password = ""; // Never null.
private String oldPassword = ""; // Never null.
private long passwordLinkExpires = 0L; // Never negative.
+ private String emailKey = "";
private boolean passwordChangeRequired = false;
private int loginCount = 0; // Never negative.
@@ -133,15 +128,27 @@ public class UserAccount {
return passwordLinkExpires;
}
- public String getPasswordLinkExpiresHash() {
- return limitStringLength(8, Authenticator.applyArgon2iEncoding(String
- .valueOf(passwordLinkExpires)));
- }
-
public void setPasswordLinkExpires(long passwordLinkExpires) {
this.passwordLinkExpires = Math.max(0, passwordLinkExpires);
}
+ public void generateEmailKey() {
+ boolean useLetters = true;
+ boolean useNumbers = true;
+ int length = 64;
+ emailKey = RandomStringUtils.random(length, useLetters, useNumbers);
+ }
+
+ public void setEmailKey(String emailKey) {
+ if (emailKey != null) {
+ this.emailKey = emailKey;
+ }
+ }
+
+ public String getEmailKey() {
+ return emailKey;
+ }
+
public boolean isPasswordChangeRequired() {
return passwordChangeRequired;
}
@@ -247,6 +254,7 @@ public class UserAccount {
+ (", oldPassword=" + oldPassword)
+ (", argon2password=" + argon2Password)
+ (", passwordLinkExpires=" + passwordLinkExpires)
+ + (", emailKey =" + emailKey)
+ (", passwordChangeRequired=" + passwordChangeRequired)
+ (", externalAuthOnly=" + externalAuthOnly)
+ (", loginCount=" + loginCount) + (", status=" + status)
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/config/ConfigurationProperties.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/config/ConfigurationProperties.java
index 49fe5e81d..ee27feef3 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/config/ConfigurationProperties.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/config/ConfigurationProperties.java
@@ -109,7 +109,7 @@ public abstract class ConfigurationProperties {
throw new NullPointerException("bean may not be null.");
}
context.setAttribute(ATTRIBUTE_NAME, bean);
- log.info(bean);
+ log.debug(bean);
}
/** Package access, so unit tests can call it. */
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/config/ConfigurationPropertiesImpl.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/config/ConfigurationPropertiesImpl.java
index 929c76dfd..3e9951882 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/config/ConfigurationPropertiesImpl.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/config/ConfigurationPropertiesImpl.java
@@ -32,10 +32,8 @@ public class ConfigurationPropertiesImpl extends ConfigurationProperties {
public ConfigurationPropertiesImpl(InputStream stream,
Map preemptiveProperties,
- Map buildProperties,
- Map contextProperties) throws IOException {
+ Map buildProperties) throws IOException {
Map map = new HashMap<>(buildProperties);
- map.putAll(contextProperties);
Properties props = loadFromPropertiesFile(stream);
for (String key: props.stringPropertyNames()) {
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/config/ConfigurationPropertiesSetup.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/config/ConfigurationPropertiesSetup.java
index d0e20b35a..54d0ac90d 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/config/ConfigurationPropertiesSetup.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/config/ConfigurationPropertiesSetup.java
@@ -49,12 +49,12 @@ public class ConfigurationPropertiesSetup implements ServletContextListener {
/** Name of the file that contains runtime properties. */
private static final String FILE_RUNTIME_PROPERTIES = "runtime.properties";
- /** Fall-back name of the file that contains runtime properties. */
- private static final String FILE_DEFAULT_RUNTIME_PROPERTIES = "default.runtime.properties";
-
/** Configuration property to store the Vitro home directory */
private static final String VHD_CONFIGURATION_PROPERTY = "vitro.home";
+ /** Configuration property used to determine if there are runtime.properties files in multiple locations **/
+ static final String RP_MULTIPLE = "rp.multiple";
+
@Override
public void contextInitialized(ServletContextEvent sce) {
ServletContext ctx = sce.getServletContext();
@@ -69,17 +69,18 @@ public class ConfigurationPropertiesSetup implements ServletContextListener {
File vitroHomeDirConfig = new File(vitroHomeDir.getPath()
.concat(File.separator).concat("config"));
+ String rpfLocation = findMultipleRuntimePropertiesFiles(
+ vitroHomeDir, vitroHomeDirConfig);
+
File runtimePropertiesFile = locateRuntimePropertiesFile(
- vitroHomeDirConfig, ss);
+ vitroHomeDir, vitroHomeDirConfig, ss);
stream = new FileInputStream(runtimePropertiesFile);
Map preempts = createPreemptiveProperties(
- VHD_CONFIGURATION_PROPERTY, vitroHomeDir);
+ VHD_CONFIGURATION_PROPERTY, vitroHomeDir, RP_MULTIPLE, rpfLocation);
ConfigurationPropertiesImpl bean = new ConfigurationPropertiesImpl(
- stream, preempts,
- new BuildProperties(ctx).getMap(),
- new ContextProperties().getMap());
+ stream, preempts, new BuildProperties(ctx).getMap());
ConfigurationProperties.setBean(ctx, bean);
ss.info(this, "Loaded " + bean.getPropertyMap().size()
@@ -98,36 +99,53 @@ public class ConfigurationPropertiesSetup implements ServletContextListener {
}
}
- private File locateRuntimePropertiesFile(File vitroHomeDirConfig, StartupStatus ss) {
+ private String findMultipleRuntimePropertiesFiles(File vitroHomeDir,
+ File vitroHomeDirConfig) {
- // First look for the user-customized runtime.properties
- File rpf = new File(vitroHomeDirConfig, FILE_RUNTIME_PROPERTIES);
+ File rpf = new File(vitroHomeDir, FILE_RUNTIME_PROPERTIES);
+ File rpfc = new File(vitroHomeDirConfig, FILE_RUNTIME_PROPERTIES);
- // Have we found a suitable runtime.properties file?
- if (!rpf.exists() || !rpf.isFile() || !rpf.canRead()) {
+ if (rpf.exists() && !rpfc.exists()) {
+ return "home";
+ } else if (rpf.exists() && rpfc.exists()) {
+ return "both";
+ } else if (rpfc.exists()) {
+ return "config";
+ } else {
+ throw new IllegalStateException("Did not find '"
+ + FILE_RUNTIME_PROPERTIES + "' in vitro home directory '"
+ + vitroHomeDir + "' or config directory '" + vitroHomeDirConfig + "'");
+ }
+ }
- // If not... look for the default runtime.properties
- rpf = new File(vitroHomeDirConfig, FILE_DEFAULT_RUNTIME_PROPERTIES);
+ private File locateRuntimePropertiesFile(File vitroHomeDir,
+ File vitroHomeDirConfig, StartupStatus ss) {
+
+ File rpf = new File(vitroHomeDir, FILE_RUNTIME_PROPERTIES);
+ File rpfc = new File(vitroHomeDirConfig, FILE_RUNTIME_PROPERTIES);
+
+ if (!rpf.exists()) {
+ rpf = rpfc;
}
- if (!rpf.exists() || !rpf.isFile()) {
- throw new IllegalStateException("Neither '" + FILE_RUNTIME_PROPERTIES + "' nor '" +
- FILE_DEFAULT_RUNTIME_PROPERTIES + "' were found in " +
- vitroHomeDirConfig.getAbsolutePath());
+ if (!rpf.isFile()) {
+ throw new IllegalStateException("'" + rpf.getPath()
+ + "' is not a file.");
}
if (!rpf.canRead()) {
- throw new IllegalStateException("No readable '" + FILE_RUNTIME_PROPERTIES + "' nor '" +
- FILE_DEFAULT_RUNTIME_PROPERTIES + "' files were found in " +
- vitroHomeDirConfig.getAbsolutePath());
+ throw new IllegalStateException("Cannot read '" + rpf.getPath()
+ + "'.");
}
ss.info(this, "Loading runtime properties from '" + rpf.getPath() + "'");
return rpf;
}
private Map createPreemptiveProperties(
- String propertyVitroHome, File vitroHomeDir) {
+ String propertyVitroHome, File vitroHomeDir, String propertyRpfMultiple,
+ String rpfLocation) {
Map map = new HashMap();
map.put(propertyVitroHome, vitroHomeDir.getAbsolutePath());
+ map.put(propertyRpfMultiple, rpfLocation);
return map;
}
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/config/ConfigurationPropertiesSmokeTests.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/config/ConfigurationPropertiesSmokeTests.java
index d460edba2..7c5e630f1 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/config/ConfigurationPropertiesSmokeTests.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/config/ConfigurationPropertiesSmokeTests.java
@@ -48,6 +48,7 @@ public class ConfigurationPropertiesSmokeTests implements
StartupStatus ss = StartupStatus.getBean(ctx);
checkDefaultNamespace(ctx, props, ss);
+ checkMultipleRPFs(ctx, props, ss);
checkLanguages(ctx, props, ss);
checkEncryptionParameters(props, ss);
@@ -85,6 +86,32 @@ public class ConfigurationPropertiesSmokeTests implements
}
}
+ /**
+ * Warn if runtime.properties exists in multiple locations
+ * or is located vivo.home instead of vivo.home/config
+ */
+ private void checkMultipleRPFs(ServletContext ctx,
+ ConfigurationProperties props, StartupStatus ss) {
+ String rpfStatus = props.getProperty(ConfigurationPropertiesSetup.RP_MULTIPLE);
+
+ if (rpfStatus.equals("both")) {
+ ss.warning(this,
+ "Deprecation warning: Files matching the name 'runtime.properties' "
+ + "were found in both vivo.home and vivo.home/config. Using "
+ + "the file in vivo.home. Future releases may require "
+ + "runtime.properties be placed in vivo.home/config.");
+ }
+
+ if (rpfStatus.equals("home")) {
+ ss.warning(this,
+ "Deprecation warning: runtime.properties was found in the "
+ + "vivo.home directory. The recommended directory for "
+ + "runtime.properties is now vivo.home/config. Future releases "
+ + "may require runtime.properties be placed in "
+ + "vivo.home/config.");
+ }
+ }
+
/**
* Warn if we set up the languages incorrectly:
*
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/config/ContextProperties.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/config/ContextProperties.java
deleted file mode 100644
index a7e04b0e9..000000000
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/config/ContextProperties.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/* $This file is distributed under the terms of the license in LICENSE$ */
-
-package edu.cornell.mannlib.vitro.webapp.config;
-
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.naming.InitialContext;
-import javax.naming.NamingException;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-
-/**
- * Obtains and provides the properties from the web application's context.xml
- *
- * @author awoods
- * @since 2020-10-23
- */
-public class ContextProperties {
- private static final Log log = LogFactory.getLog(ContextProperties.class);
-
- private static final String DEFAULT_NAMESPACE_JNDI_PATH = "java:comp/env/vitro/defaultNamespace";
- private static final String ROOT_USER_ADDRESS_JNDI_PATH = "java:comp/env/vitro/rootUserAddress";
- private static final String APP_NAME_JNDI_PATH = "java:comp/env/vitro/appName";
-
- private static final String DEFAULT_NAMESPACE_KEY = "Vitro.defaultNamespace";
- private static final String ROOT_USER_ADDRESS_KEY = "rootUser.emailAddress";
- private static final String APP_NAME_KEY = "app-name";
-
- private final Map propertyMap;
-
- public ContextProperties() {
- Map map = new HashMap<>();
-
- // Find default namespace
- map.put(DEFAULT_NAMESPACE_KEY, findJndiProperty(DEFAULT_NAMESPACE_JNDI_PATH));
-
- // Find root user email address
- map.put(ROOT_USER_ADDRESS_KEY, findJndiProperty(ROOT_USER_ADDRESS_JNDI_PATH));
-
- // Find application name
- map.put(APP_NAME_KEY, findJndiProperty(APP_NAME_JNDI_PATH));
-
- propertyMap = Collections.unmodifiableMap(map);
- }
-
- public static String findJndiProperty(String jndiProperty) {
- try {
- return (String) new InitialContext().lookup(jndiProperty);
-
- } catch (NamingException e) {
- log.error("Unable to find name in JNDI: " + jndiProperty, e);
-
- StringBuilder msg = new StringBuilder("\n====================\n");
- msg.append("Error loading JNDI property: ");
- msg.append(jndiProperty);
- msg.append("\n");
- msg.append("\tAn application context XML file (named after deployed war file, e.g. vivo.xml) ");
- msg.append("must be placed in servlet container.\n");
- msg.append("\tFor Tomcat, see documentation for location of file: \n");
- msg.append("\t\thttps://tomcat.apache.org/tomcat-9.0-doc/config/context.html#Defining_a_context \n");
- msg.append("\tThe common location on the server is: $CATALINA_BASE/conf/[enginename]/[hostname]/ \n");
- msg.append("\t\te.g. /var/lib/tomcat9/conf/Catalina/localhost/vivo.xml\n");
- msg.append("\tAn example 'context.xml' file is in the META-INF directory of this project.\n");
- msg.append("====================\n");
- throw new RuntimeException(msg.toString(), e);
- }
- }
-
- public Map getMap() {
- return this.propertyMap;
- }
-
-}
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/accounts/UserAccountsSelector.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/accounts/UserAccountsSelector.java
index 6caadf24c..d878ae315 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/accounts/UserAccountsSelector.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/accounts/UserAccountsSelector.java
@@ -249,6 +249,7 @@ public class UserAccountsSelector {
user.setMd5Password(ifLiteralPresent(solution, "md5pwd", ""));
user.setArgon2Password(ifLiteralPresent(solution, "a2pwd", ""));
user.setPasswordLinkExpires(ifLongPresent(solution, "expire", 0L));
+ user.setEmailKey(ifLiteralPresent(solution, "emailKey", ""));
user.setLoginCount(ifIntPresent(solution, "count", 0));
user.setLastLoginTime(ifLongPresent(solution, "lastLogin", 0));
user.setStatus(parseStatus(solution, "status", null));
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/accounts/admin/UserAccountsAddPage.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/accounts/admin/UserAccountsAddPage.java
index bdbc5dbce..7fc4181da 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/accounts/admin/UserAccountsAddPage.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/accounts/admin/UserAccountsAddPage.java
@@ -156,6 +156,7 @@ public class UserAccountsAddPage extends UserAccountsPage {
u.setOldPassword("");
u.setPasswordChangeRequired(false);
u.setPasswordLinkExpires(0);
+ u.setEmailKey("");
u.setLoginCount(0);
u.setLastLoginTime(0L);
u.setStatus(Status.INACTIVE);
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/accounts/admin/UserAccountsAddPageStrategy.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/accounts/admin/UserAccountsAddPageStrategy.java
index 307ccf9f6..036d314e2 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/accounts/admin/UserAccountsAddPageStrategy.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/accounts/admin/UserAccountsAddPageStrategy.java
@@ -84,6 +84,7 @@ public abstract class UserAccountsAddPageStrategy extends UserAccountsPage {
u.setStatus(Status.ACTIVE);
} else {
u.setPasswordLinkExpires(figureExpirationDate().getTime());
+ u.generateEmailKey();
u.setStatus(Status.INACTIVE);
}
}
@@ -119,10 +120,8 @@ public abstract class UserAccountsAddPageStrategy extends UserAccountsPage {
private String buildCreatePasswordLink() {
try {
String email = page.getAddedAccount().getEmailAddress();
- String hash = page.getAddedAccount()
- .getPasswordLinkExpiresHash();
- String relativeUrl = UrlBuilder.getUrl(CREATE_PASSWORD_URL,
- "user", email, "key", hash);
+ String key = page.getAddedAccount().getEmailKey();
+ String relativeUrl = UrlBuilder.getUrl(CREATE_PASSWORD_URL, "user", email, "key", key);
URL context = new URL(vreq.getRequestURL().toString());
URL url = new URL(context, relativeUrl);
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/accounts/admin/UserAccountsEditPage.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/accounts/admin/UserAccountsEditPage.java
index 42dd4102d..13e1ab0dd 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/accounts/admin/UserAccountsEditPage.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/accounts/admin/UserAccountsEditPage.java
@@ -274,6 +274,7 @@ public class UserAccountsEditPage extends UserAccountsPage {
userAccount.setOldPassword("");
userAccount.setPasswordChangeRequired(false);
userAccount.setPasswordLinkExpires(0L);
+ userAccount.setEmailKey("");
}
if (isRootUser()) {
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/accounts/admin/UserAccountsEditPageStrategy.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/accounts/admin/UserAccountsEditPageStrategy.java
index 708f11e66..d9ee8aa14 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/accounts/admin/UserAccountsEditPageStrategy.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/accounts/admin/UserAccountsEditPageStrategy.java
@@ -82,6 +82,7 @@ public abstract class UserAccountsEditPageStrategy extends UserAccountsPage {
protected void setAdditionalProperties(UserAccount u) {
if (resetPassword && !page.isExternalAuthOnly()) {
u.setPasswordLinkExpires(figureExpirationDate().getTime());
+ u.generateEmailKey();
}
}
@@ -121,10 +122,8 @@ public abstract class UserAccountsEditPageStrategy extends UserAccountsPage {
private String buildResetPasswordLink() {
try {
String email = page.getUpdatedAccount().getEmailAddress();
- String hash = page.getUpdatedAccount()
- .getPasswordLinkExpiresHash();
- String relativeUrl = UrlBuilder.getUrl(RESET_PASSWORD_URL,
- "user", email, "key", hash);
+ String key = page.getUpdatedAccount().getEmailKey();
+ String relativeUrl = UrlBuilder.getUrl(RESET_PASSWORD_URL, "user", email, "key", key);
URL context = new URL(vreq.getRequestURL().toString());
URL url = new URL(context, relativeUrl);
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/accounts/user/UserAccountsCreatePasswordPage.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/accounts/user/UserAccountsCreatePasswordPage.java
index b9515d4c7..68daa2d67 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/accounts/user/UserAccountsCreatePasswordPage.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/accounts/user/UserAccountsCreatePasswordPage.java
@@ -36,6 +36,7 @@ public class UserAccountsCreatePasswordPage extends
userAccount.setArgon2Password(Authenticator.applyArgon2iEncoding(newPassword));
userAccount.setMd5Password("");
userAccount.setPasswordLinkExpires(0L);
+ userAccount.setEmailKey("");
userAccount.setPasswordChangeRequired(false);
userAccount.setStatus(Status.ACTIVE);
userAccountsDao.updateUserAccount(userAccount);
@@ -53,6 +54,11 @@ public class UserAccountsCreatePasswordPage extends
protected String passwordChangeNotPendingMessage() {
return i18n.text("account_already_activated", userEmail);
}
+
+ @Override
+ protected String passwordChangeInavlidKeyMessage() {
+ return i18n.text("password_change_invalid_key", userEmail);
+ }
@Override
protected String templateName() {
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/accounts/user/UserAccountsFirstTimeExternalPage.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/accounts/user/UserAccountsFirstTimeExternalPage.java
index fc11f665d..ffb34c754 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/accounts/user/UserAccountsFirstTimeExternalPage.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/accounts/user/UserAccountsFirstTimeExternalPage.java
@@ -195,6 +195,7 @@ public class UserAccountsFirstTimeExternalPage extends UserAccountsPage {
u.setExternalAuthId(externalAuthId);
u.setPasswordChangeRequired(false);
u.setPasswordLinkExpires(0);
+ u.setEmailKey("");
u.setExternalAuthOnly(true);
u.setLoginCount(0);
u.setStatus(Status.ACTIVE);
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/accounts/user/UserAccountsMyAccountPageStrategy.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/accounts/user/UserAccountsMyAccountPageStrategy.java
index 057098eea..ca895cab8 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/accounts/user/UserAccountsMyAccountPageStrategy.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/accounts/user/UserAccountsMyAccountPageStrategy.java
@@ -159,6 +159,7 @@ public abstract class UserAccountsMyAccountPageStrategy extends
userAccount.setMd5Password("");
userAccount.setPasswordChangeRequired(false);
userAccount.setPasswordLinkExpires(0L);
+ userAccount.setEmailKey("");
}
}
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/accounts/user/UserAccountsPasswordBasePage.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/accounts/user/UserAccountsPasswordBasePage.java
index 3923c17b2..d4cc56f03 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/accounts/user/UserAccountsPasswordBasePage.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/accounts/user/UserAccountsPasswordBasePage.java
@@ -103,12 +103,12 @@ public abstract class UserAccountsPasswordBasePage extends UserAccountsPage {
return;
}
- String expectedKey = userAccount.getPasswordLinkExpiresHash();
- if (!key.equals(expectedKey)) {
+ String expectedKey = userAccount.getEmailKey();
+ if (key.isEmpty() || !key.equals(expectedKey)) {
log.warn("Password request for '" + userEmail + "' is bogus: key ("
+ key + ") doesn't match expected key (" + expectedKey
+ ")");
- bogusMessage = passwordChangeNotPendingMessage();
+ bogusMessage = passwordChangeInavlidKeyMessage();
return;
}
@@ -153,7 +153,7 @@ public abstract class UserAccountsPasswordBasePage extends UserAccountsPage {
body.put("minimumLength", UserAccount.MIN_PASSWORD_LENGTH);
body.put("maximumLength", UserAccount.MAX_PASSWORD_LENGTH);
body.put("userAccount", userAccount);
- body.put("key", userAccount.getPasswordLinkExpiresHash());
+ body.put("key", userAccount.getEmailKey());
body.put("newPassword", newPassword);
body.put("confirmPassword", confirmPassword);
body.put("formUrls", buildUrlsMap());
@@ -176,6 +176,8 @@ public abstract class UserAccountsPasswordBasePage extends UserAccountsPage {
protected abstract String alreadyLoggedInMessage(String currentUserEmail);
protected abstract String passwordChangeNotPendingMessage();
+
+ protected abstract String passwordChangeInavlidKeyMessage();
protected abstract String templateName();
}
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/accounts/user/UserAccountsResetPasswordPage.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/accounts/user/UserAccountsResetPasswordPage.java
index 712df1b40..f865cbe94 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/accounts/user/UserAccountsResetPasswordPage.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/accounts/user/UserAccountsResetPasswordPage.java
@@ -55,6 +55,11 @@ public class UserAccountsResetPasswordPage extends UserAccountsPasswordBasePage
protected String passwordChangeNotPendingMessage() {
return i18n.text("password_change_not_pending", userEmail);
}
+
+ @Override
+ protected String passwordChangeInavlidKeyMessage() {
+ return i18n.text("password_change_invalid_key", userEmail);
+ }
@Override
protected String templateName() {
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/authenticate/BasicAuthenticator.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/authenticate/BasicAuthenticator.java
index f6f95081b..34fc6a01d 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/authenticate/BasicAuthenticator.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/authenticate/BasicAuthenticator.java
@@ -134,6 +134,7 @@ public class BasicAuthenticator extends Authenticator {
userAccount.setMd5Password("");
userAccount.setPasswordChangeRequired(false);
userAccount.setPasswordLinkExpires(0L);
+ userAccount.setEmailKey("");
getUserAccountsDao().updateUserAccount(userAccount);
}
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/authenticate/LoginRedirector.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/authenticate/LoginRedirector.java
index ffc18f805..65b8a2310 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/authenticate/LoginRedirector.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/authenticate/LoginRedirector.java
@@ -108,7 +108,9 @@ public class LoginRedirector {
throws IOException {
try {
DisplayMessage.setMessage(request, assembleWelcomeMessage());
- response.sendRedirect(getRedirectionUriForLoggedInUser());
+ String redirectUrl = getRedirectionUriForLoggedInUser();
+ log.debug("Sending redirect to path: " + redirectUrl);
+ response.sendRedirect(redirectUrl);
} catch (IOException e) {
log.debug("Problem with re-direction", e);
response.sendRedirect(getApplicationHomePageUrl());
@@ -175,21 +177,13 @@ public class LoginRedirector {
}
}
- /**
- * The application home page can be overridden by an attribute in the
- * ServletContext. Further, it can either be an absolute URL, or it can be
- * relative to the application. Weird.
- */
private String getApplicationHomePageUrl() {
- String contextRedirect = (String) session.getServletContext()
- .getAttribute("postLoginRequest");
- if (contextRedirect != null) {
- if (contextRedirect.indexOf(":") == -1) {
- return request.getContextPath() + contextRedirect;
- } else {
- return contextRedirect;
- }
+ String contextPath = request.getContextPath();
+ if (contextPath.equals("")) {
+ return "/";
+ }
+ else {
+ return contextPath;
}
- return request.getContextPath();
}
}
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/freemarker/ViewLabelsServlet.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/freemarker/ViewLabelsServlet.java
index 1425b10f7..f68db8d56 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/freemarker/ViewLabelsServlet.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/freemarker/ViewLabelsServlet.java
@@ -11,28 +11,29 @@ import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
+import java.util.Set;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import org.apache.commons.lang3.LocaleUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-
import org.apache.jena.query.QuerySolution;
import org.apache.jena.query.ResultSet;
import org.apache.jena.rdf.model.Literal;
import edu.cornell.mannlib.vitro.webapp.beans.Individual;
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
-import edu.cornell.mannlib.vitro.webapp.controller.freemarker.FreemarkerHttpServlet;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.ExceptionResponseValues;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.ResponseValues;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.TemplateResponseValues;
import edu.cornell.mannlib.vitro.webapp.dao.jena.QueryUtils;
import edu.cornell.mannlib.vitro.webapp.i18n.selection.SelectedLocale;
+import edu.cornell.mannlib.vitro.webapp.rdfservice.filter.LanguageFilteringUtils;
/*Servlet to view all labels in various languages for individual*/
@@ -47,12 +48,13 @@ public class ViewLabelsServlet extends FreemarkerHttpServlet{
String subjectUri = vreq.getParameter("subjectUri");
body.put("subjectUri", subjectUri);
try {
- //Get all language codes/labels in the system, and this list is sorted by language name
- List> locales = this.getLocales(vreq);
+ //the labels already added by the user
+ ArrayList existingLabels = this.getExistingLabels(subjectUri, vreq);
+ //Get all language codes/labels used in the list of existing labels
+ List> locales = this.getLocales(vreq, existingLabels);
//Get code to label hashmap - we use this to get the language name for the language code returned in the rdf literal
HashMap localeCodeToNameMap = this.getFullCodeToLanguageNameMap(locales);
- //the labels already added by the user
- ArrayList existingLabels = this.getExistingLabels(subjectUri, vreq);
+
//existing labels keyed by language name and each of the list of labels is sorted by language name
HashMap> existingLabelsByLanguageName = this.getLabelsSortedByLanguageName(existingLabels, localeCodeToNameMap, vreq, subjectUri);
//Get available locales for the drop down for adding a new label, also sorted by language name
@@ -137,20 +139,26 @@ public class ViewLabelsServlet extends FreemarkerHttpServlet{
doGet(request, response);
}
- //get locales
- public List> getLocales(VitroRequest vreq) {
- List selectables = SelectedLocale.getSelectableLocales(vreq);
- if (selectables.isEmpty()) {
+ //get locales present in list of literals
+ public List> getLocales(VitroRequest vreq,
+ List existingLiterals) {
+ Set locales = new HashSet();
+ for(Literal literal : existingLiterals) {
+ String language = literal.getLanguage();
+ if(!StringUtils.isEmpty(language)) {
+ locales.add(LanguageFilteringUtils.languageToLocale(language));
+ }
+ }
+ if (locales.isEmpty()) {
return Collections.emptyList();
}
List> list = new ArrayList>();
Locale currentLocale = SelectedLocale.getCurrentLocale(vreq);
- for (Locale locale : selectables) {
+ for (Locale locale : locales) {
try {
list.add(buildLocaleMap(locale, currentLocale));
} catch (FileNotFoundException e) {
- log.warn("Can't show the Locale selector for '" + locale
- + "': " + e);
+ log.warn("Can't show locale '" + locale + "': " + e);
}
}
@@ -188,8 +196,8 @@ public class ViewLabelsServlet extends FreemarkerHttpServlet{
ArrayList labels = new ArrayList();
try {
- //We want to get the labels for all the languages, not just the display language
- ResultSet results = QueryUtils.getLanguageNeutralQueryResults(queryStr, vreq);
+ // Show only labels with current language filtering
+ ResultSet results = QueryUtils.getQueryResults(queryStr, vreq);
while (results.hasNext()) {
QuerySolution soln = results.nextSolution();
Literal nodeLiteral = soln.get("label").asLiteral();
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/individual/IndividualResponseBuilder.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/individual/IndividualResponseBuilder.java
index c89119f5a..0f7154c88 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/individual/IndividualResponseBuilder.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/individual/IndividualResponseBuilder.java
@@ -6,14 +6,13 @@ import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
-import org.apache.jena.rdf.model.RDFNode;
-import edu.cornell.mannlib.vitro.webapp.web.templatemodels.individual.IndividualTemplateModelBuilder;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-
import org.apache.jena.query.QuerySolution;
import org.apache.jena.query.ResultSet;
@@ -36,6 +35,7 @@ import edu.cornell.mannlib.vitro.webapp.i18n.selection.SelectedLocale;
import edu.cornell.mannlib.vitro.webapp.utils.dataGetter.ExecuteDataRetrieval;
import edu.cornell.mannlib.vitro.webapp.web.beanswrappers.ReadOnlyBeansWrapper;
import edu.cornell.mannlib.vitro.webapp.web.templatemodels.individual.IndividualTemplateModel;
+import edu.cornell.mannlib.vitro.webapp.web.templatemodels.individual.IndividualTemplateModelBuilder;
import edu.ucsf.vitro.opensocial.OpenSocialManager;
import freemarker.ext.beans.BeansWrapper;
import freemarker.template.TemplateModel;
@@ -123,8 +123,10 @@ class IndividualResponseBuilder {
* into the data model: no real data can be modified.
*/
// body.put("individual", wrap(itm, BeansWrapper.EXPOSE_SAFE));
- body.put("labelCount", getLabelCount(itm.getUri(), vreq));
- body.put("languageCount", getLanguagesRepresentedCount(itm.getUri(), vreq));
+ LabelAndLanguageCount labelAndLanguageCount = getLabelAndLanguageCount(
+ itm.getUri(), vreq);
+ body.put("labelCount", labelAndLanguageCount.getLabelCount());
+ body.put("languageCount", labelAndLanguageCount.getLanguageCount());
//We also need to know the number of available locales
body.put("localesCount", SelectedLocale.getSelectableLocales(vreq).size());
body.put("profileType", getProfileType(itm.getUri(), vreq));
@@ -282,61 +284,103 @@ class IndividualResponseBuilder {
return map;
}
- private static String LABEL_COUNT_QUERY = ""
- + "PREFIX rdfs: \n"
- + "SELECT ( str(COUNT(?label)) AS ?labelCount ) WHERE { \n"
- + " ?subject rdfs:label ?label \n"
- + " FILTER isLiteral(?label) \n"
- + "}" ;
-
- private static String DISTINCT_LANGUAGE_QUERY = ""
+ private static String LABEL_QUERY = ""
+ "PREFIX rdfs: \n"
- + "SELECT ( str(COUNT(DISTINCT lang(?label))) AS ?languageCount ) WHERE { \n"
+ + "SELECT ?label WHERE { \n"
+ " ?subject rdfs:label ?label \n"
+ " FILTER isLiteral(?label) \n"
+ "}" ;
+
+// Queries that were previously used for counts via RDFService that didn't
+// filter results by language. With language filtering, aggregate
+// functions like COUNT() cannot be used.
+
+// private static String LABEL_COUNT_QUERY = ""
+// + "PREFIX rdfs: \n"
+// + "SELECT ( str(COUNT(?label)) AS ?labelCount ) WHERE { \n"
+// + " ?subject rdfs:label ?label \n"
+// + " FILTER isLiteral(?label) \n"
+// + "}" ;
- private static Integer getLabelCount(String subjectUri, VitroRequest vreq) {
- String queryStr = QueryUtils.subUriForQueryVar(LABEL_COUNT_QUERY, "subject", subjectUri);
+// private static String DISTINCT_LANGUAGE_QUERY = ""
+// + "PREFIX rdfs: \n"
+// + "SELECT ( str(COUNT(DISTINCT lang(?label))) AS ?languageCount ) WHERE { \n"
+// + " ?subject rdfs:label ?label \n"
+// + " FILTER isLiteral(?label) \n"
+// + "}" ;
+
+ private static LabelAndLanguageCount getLabelAndLanguageCount(
+ String subjectUri, VitroRequest vreq) {
+ // 1.12.0 Now filtering to only the labels for the current locale so as
+ // to be consistent with other editing forms. Because the language
+ // filter can only act on a result set containing actual literals,
+ // we can't do the counting with a COUNT() in the query itself. So
+ // we will now use the LABEL_QUERY instead of LABEL_COUNT_QUERY and
+ // count the rows and the number of distinct languages represented.
+ Set distinctLanguages = new HashSet();
+ String queryStr = QueryUtils.subUriForQueryVar(LABEL_QUERY, "subject", subjectUri);
log.debug("queryStr = " + queryStr);
- int theCount = 0;
- try {
- //ResultSet results = QueryUtils.getQueryResults(queryStr, vreq);
- //Get query results across all languages in order for template to show manage labels link correctly
- ResultSet results = QueryUtils.getLanguageNeutralQueryResults(queryStr, vreq);
- if (results.hasNext()) {
- QuerySolution soln = results.nextSolution();
- RDFNode labelCount = soln.get("labelCount");
- if (labelCount != null && labelCount.isLiteral()) {
- theCount = labelCount.asLiteral().getInt();
+ int labelCount = 0;
+ try {
+ ResultSet results = QueryUtils.getQueryResults(queryStr, vreq);
+ while(results.hasNext()) {
+ QuerySolution qsoln = results.next();
+ labelCount++;
+ String lang = qsoln.getLiteral("label").getLanguage();
+ if(lang == null) {
+ lang = "";
}
+ distinctLanguages.add(lang);
}
} catch (Exception e) {
log.error(e, e);
}
- return theCount;
+ return new LabelAndLanguageCount(labelCount, distinctLanguages.size());
+ }
+
+ private static class LabelAndLanguageCount {
+
+ private Integer labelCount;
+ private Integer languageCount;
+
+ public LabelAndLanguageCount(Integer labelCount, Integer languageCount) {
+ this.labelCount = labelCount;
+ this.languageCount = languageCount;
+ }
+
+ public Integer getLabelCount() {
+ return this.labelCount;
+ }
+
+ public Integer getLanguageCount() {
+ return this.languageCount;
+ }
+
}
//what is the number of languages represented across the labels
- private static Integer getLanguagesRepresentedCount(String subjectUri, VitroRequest vreq) {
- String queryStr = QueryUtils.subUriForQueryVar(DISTINCT_LANGUAGE_QUERY, "subject", subjectUri);
- log.debug("queryStr = " + queryStr);
- int theCount = 0;
- try {
-
- ResultSet results = QueryUtils.getLanguageNeutralQueryResults(queryStr, vreq);
- if (results.hasNext()) {
- QuerySolution soln = results.nextSolution();
- RDFNode languageCount = soln.get("languageCount");
- if (languageCount != null && languageCount.isLiteral()) {
- theCount = languageCount.asLiteral().getInt();
- }
- }
- } catch (Exception e) {
- log.error(e, e);
- }
- return theCount;
- }
+ // This version not compatible with language-filtering RDF services
+// private static Integer getLanguagesRepresentedCount(String subjectUri, VitroRequest vreq) {
+// String queryStr = QueryUtils.subUriForQueryVar(DISTINCT_LANGUAGE_QUERY, "subject", subjectUri);
+// log.debug("queryStr = " + queryStr);
+// int theCount = 0;
+// try {
+//
+// ResultSet results = QueryUtils.getLanguageNeutralQueryResults(queryStr, vreq);
+// if (results.hasNext()) {
+// QuerySolution soln = results.nextSolution();
+// RDFNode languageCount = soln.get("languageCount");
+// if (languageCount != null && languageCount.isLiteral()) {
+// theCount = languageCount.asLiteral().getInt();
+// log.info("Language count is " + theCount);
+// }
+// }
+// } catch (Exception e) {
+// log.error(e, e);
+// }
+// log.info("Returning language count " + theCount);
+// return theCount;
+// }
private static String PROFILE_TYPE_QUERY = ""
+ "PREFIX display: \n"
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/VitroVocabulary.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/VitroVocabulary.java
index fdd9387c0..fba900cad 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/VitroVocabulary.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/VitroVocabulary.java
@@ -155,6 +155,7 @@ public class VitroVocabulary {
public static final String USERACCOUNT_LAST_LOGIN_TIME = VITRO_AUTH + "lastLoginTime";
public static final String USERACCOUNT_STATUS = VITRO_AUTH + "status";
public static final String USERACCOUNT_PASSWORD_LINK_EXPIRES = VITRO_AUTH + "passwordLinkExpires";
+ public static final String USERACCOUNT_EMAIL_KEY = VITRO_AUTH + "emailKey";
public static final String USERACCOUNT_PASSWORD_CHANGE_REQUIRED = VITRO_AUTH + "passwordChangeRequired";
public static final String USERACCOUNT_EXTERNAL_AUTH_ID = VITRO_AUTH + "externalAuthId";
public static final String USERACCOUNT_EXTERNAL_AUTH_ONLY = VITRO_AUTH + "externalAuthOnly";
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/jena/JenaBaseDaoCon.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/jena/JenaBaseDaoCon.java
index d618ac804..71fd6447a 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/jena/JenaBaseDaoCon.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/jena/JenaBaseDaoCon.java
@@ -121,6 +121,7 @@ public class JenaBaseDaoCon {
protected DatatypeProperty USERACCOUNT_LAST_LOGIN_TIME = _constModel.createDatatypeProperty(VitroVocabulary.USERACCOUNT_LAST_LOGIN_TIME);
protected DatatypeProperty USERACCOUNT_STATUS = _constModel.createDatatypeProperty(VitroVocabulary.USERACCOUNT_STATUS);
protected DatatypeProperty USERACCOUNT_PASSWORD_LINK_EXPIRES = _constModel.createDatatypeProperty(VitroVocabulary.USERACCOUNT_PASSWORD_LINK_EXPIRES);
+ protected DatatypeProperty USERACCOUNT_EMAIL_KEY = _constModel.createDatatypeProperty(VitroVocabulary.USERACCOUNT_EMAIL_KEY);
protected DatatypeProperty USERACCOUNT_PASSWORD_CHANGE_REQUIRED = _constModel.createDatatypeProperty(VitroVocabulary.USERACCOUNT_PASSWORD_CHANGE_REQUIRED);
protected DatatypeProperty USERACCOUNT_EXTERNAL_AUTH_ID = _constModel.createDatatypeProperty(VitroVocabulary.USERACCOUNT_EXTERNAL_AUTH_ID);
protected DatatypeProperty USERACCOUNT_EXTERNAL_AUTH_ONLY = _constModel.createDatatypeProperty(VitroVocabulary.USERACCOUNT_EXTERNAL_AUTH_ONLY);
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/jena/RDFServiceGraph.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/jena/RDFServiceGraph.java
index ab509c82e..f6c3b4dec 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/jena/RDFServiceGraph.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/jena/RDFServiceGraph.java
@@ -12,10 +12,10 @@ import java.util.function.Supplier;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-
import org.apache.jena.graph.Capabilities;
import org.apache.jena.graph.Graph;
import org.apache.jena.graph.GraphEventManager;
+import org.apache.jena.graph.GraphListener;
import org.apache.jena.graph.GraphStatisticsHandler;
import org.apache.jena.graph.Node;
import org.apache.jena.graph.TransactionHandler;
@@ -23,7 +23,6 @@ import org.apache.jena.graph.Triple;
import org.apache.jena.graph.impl.GraphWithPerform;
import org.apache.jena.graph.impl.SimpleEventManager;
import org.apache.jena.query.QuerySolution;
-import org.apache.jena.rdf.listeners.StatementListener;
import org.apache.jena.rdf.model.Model;
import org.apache.jena.rdf.model.ModelFactory;
import org.apache.jena.rdf.model.StmtIterator;
@@ -409,7 +408,18 @@ public class RDFServiceGraph implements GraphWithPerform {
@Override
public GraphEventManager getEventManager() {
if (eventManager == null) {
- eventManager = new SimpleEventManager(this);
+ eventManager = new SimpleEventManager() {
+ @Override
+ public void notifyEvent(Graph g, Object event) {
+ ChangeSet changeSet = rdfService.manufactureChangeSet();
+ changeSet.addPreChangeEvent(event);
+ try {
+ rdfService.changeSetUpdate(changeSet);
+ } catch (RDFServiceException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ };
}
return eventManager;
}
@@ -595,21 +605,7 @@ public class RDFServiceGraph implements GraphWithPerform {
}
public static Model createRDFServiceModel(final RDFServiceGraph g) {
- Model m = VitroModelFactory.createModelForGraph(g);
- m.register(new StatementListener() {
- @Override
- public void notifyEvent(Model m, Object event) {
- ChangeSet changeSet = g.getRDFService().manufactureChangeSet();
- changeSet.addPreChangeEvent(event);
- try {
- g.getRDFService().changeSetUpdate(changeSet);
- } catch (RDFServiceException e) {
- throw new RuntimeException(e);
- }
- }
-
- });
- return m;
+ return VitroModelFactory.createModelForGraph(g);
}
@Override
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/jena/UserAccountsDaoJena.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/jena/UserAccountsDaoJena.java
index 73557fd5c..29c1d5a29 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/jena/UserAccountsDaoJena.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/jena/UserAccountsDaoJena.java
@@ -4,7 +4,6 @@ package edu.cornell.mannlib.vitro.webapp.dao.jena;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Random;
@@ -98,6 +97,8 @@ public class UserAccountsDaoJena extends JenaBaseDao implements UserAccountsDao
u.setOldPassword(getPropertyStringValue(r, USERACCOUNT_OLD_PASSWORD));
u.setPasswordLinkExpires(getPropertyLongValue(r,
USERACCOUNT_PASSWORD_LINK_EXPIRES));
+ u.setEmailKey(getPropertyStringValue(r,USERACCOUNT_EMAIL_KEY));
+
u.setPasswordChangeRequired(getPropertyBooleanValue(r,
USERACCOUNT_PASSWORD_CHANGE_REQUIRED));
u.setExternalAuthOnly(getPropertyBooleanValue(r,
@@ -240,6 +241,8 @@ public class UserAccountsDaoJena extends JenaBaseDao implements UserAccountsDao
userAccount.getLoginCount(), model);
addPropertyLongValue(res, USERACCOUNT_LAST_LOGIN_TIME,
userAccount.getLastLoginTime(), model);
+ addPropertyStringValue(res, USERACCOUNT_EMAIL_KEY,
+ userAccount.getEmailKey(), model);
if (userAccount.getStatus() != null) {
addPropertyStringValue(res, USERACCOUNT_STATUS, userAccount
.getStatus().toString(), model);
@@ -306,6 +309,8 @@ public class UserAccountsDaoJena extends JenaBaseDao implements UserAccountsDao
userAccount.getLoginCount(), model);
updatePropertyLongValue(res, USERACCOUNT_LAST_LOGIN_TIME,
userAccount.getLastLoginTime(), model);
+ updatePropertyStringValue(res, USERACCOUNT_EMAIL_KEY,
+ userAccount.getEmailKey(), model);
if (userAccount.getStatus() == null) {
updatePropertyStringValue(res, USERACCOUNT_STATUS, null, model);
} else {
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/generators/BaseEditConfigurationGenerator.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/generators/BaseEditConfigurationGenerator.java
index f5de55789..55cff7761 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/generators/BaseEditConfigurationGenerator.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/generators/BaseEditConfigurationGenerator.java
@@ -16,6 +16,7 @@ import edu.cornell.mannlib.vitro.webapp.edit.n3editing.VTwo.EditConfigurationVTw
import edu.cornell.mannlib.vitro.webapp.edit.n3editing.configuration.IdModelSelector;
import edu.cornell.mannlib.vitro.webapp.edit.n3editing.configuration.StandardModelSelector;
import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess;
+import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess.LanguageOption;
public abstract class BaseEditConfigurationGenerator implements EditConfigurationGenerator {
@@ -63,6 +64,7 @@ public abstract class BaseEditConfigurationGenerator implements EditConfiguratio
setupModelSelectorsFromVitroRequest(vreq, editConfig);
OntModel queryModel = ModelAccess.on(vreq).getOntModel();
+ OntModel languageNeutralModel = vreq.getLanguageNeutralUnionFullModel();
if( editConfig.getSubjectUri() == null)
editConfig.setSubjectUri( EditConfigurationUtils.getSubjectUri(vreq));
@@ -78,7 +80,10 @@ public abstract class BaseEditConfigurationGenerator implements EditConfiguratio
editConfig.prepareForObjPropUpdate(queryModel);
} else if( dataKey != null ) { // edit of a data prop statement
//do nothing since the data prop form generator must take care of it
- editConfig.prepareForDataPropUpdate(queryModel, vreq.getWebappDaoFactory().getDataPropertyDao());
+ // Use language-neutral model to ensure that a data property statement
+ // is found for any literal hash, even if the UI locale is changed.
+ editConfig.prepareForDataPropUpdate(languageNeutralModel,
+ vreq.getWebappDaoFactory().getDataPropertyDao());
} else{
//this might be a create new or a form
editConfig.prepareForNonUpdate(queryModel);
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/generators/ManageLabelsForIndividualGenerator.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/generators/ManageLabelsForIndividualGenerator.java
index d2a109895..3f7e912bb 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/generators/ManageLabelsForIndividualGenerator.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/generators/ManageLabelsForIndividualGenerator.java
@@ -15,6 +15,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
+import java.util.Set;
import javax.servlet.http.HttpSession;
@@ -41,6 +42,7 @@ import edu.cornell.mannlib.vitro.webapp.edit.n3editing.VTwo.fields.FieldVTwo;
import edu.cornell.mannlib.vitro.webapp.edit.n3editing.configuration.preprocessors.FoafNameToRdfsLabelPreprocessor;
import edu.cornell.mannlib.vitro.webapp.edit.n3editing.configuration.preprocessors.ManageLabelsForIndividualPreprocessor;
import edu.cornell.mannlib.vitro.webapp.i18n.selection.SelectedLocale;
+import edu.cornell.mannlib.vitro.webapp.rdfservice.filter.LanguageFilteringUtils;
import edu.cornell.mannlib.vitro.webapp.web.templatemodels.individual.DataPropertyStatementTemplateModel;
/**
@@ -202,12 +204,12 @@ public class ManageLabelsForIndividualGenerator extends BaseEditConfigurationGen
private void addFormSpecificData(EditConfigurationVTwo config,
VitroRequest vreq) {
- //Get all language codes/labels in the system, and this list is sorted by language name
- List> locales = this.getLocales(vreq);
+ //the labels already added by the user
+ ArrayList existingLabels = this.getExistingLabels(config.getSubjectUri(), vreq);
+ //Get language codes/labels for languages present in the existing labels
+ List> locales = this.getLocales(vreq, existingLabels);
//Get code to label hashmap - we use this to get the language name for the language code returned in the rdf literal
- HashMap localeCodeToNameMap = this.getFullCodeToLanguageNameMap(locales);
- //the labels already added by the user
- ArrayList existingLabels = this.getExistingLabels(config.getSubjectUri(), vreq);
+ HashMap localeCodeToNameMap = this.getFullCodeToLanguageNameMap(locales);
int numberExistingLabels = existingLabels.size();
//existing labels keyed by language name and each of the list of labels is sorted by language name
HashMap> existingLabelsByLanguageName = this.getLabelsSortedByLanguageName(existingLabels, localeCodeToNameMap, config, vreq);
@@ -373,8 +375,9 @@ public class ManageLabelsForIndividualGenerator extends BaseEditConfigurationGen
ArrayList labels = new ArrayList();
try {
- //We want to get the labels for all the languages, not just the display language
- ResultSet results = QueryUtils.getLanguageNeutralQueryResults(queryStr, vreq);
+ // Get results filtered to current locale so as to be consistent
+ // with other editing forms.
+ ResultSet results = QueryUtils.getQueryResults(queryStr, vreq);
while (results.hasNext()) {
QuerySolution soln = results.nextSolution();
Literal nodeLiteral = soln.get("label").asLiteral();
@@ -401,30 +404,32 @@ public class ManageLabelsForIndividualGenerator extends BaseEditConfigurationGen
return template;
}
+ //get locales present in list of literals
+ public List> getLocales(VitroRequest vreq,
+ List existingLiterals) {
+ Set locales = new HashSet();
+ for(Literal literal : existingLiterals) {
+ String language = literal.getLanguage();
+ if(!StringUtils.isEmpty(language)) {
+ locales.add(LanguageFilteringUtils.languageToLocale(language));
+ }
+ }
+ if (locales.isEmpty()) {
+ return Collections.emptyList();
+ }
+ List> list = new ArrayList>();
+ Locale currentLocale = SelectedLocale.getCurrentLocale(vreq);
+ for (Locale locale : locales) {
+ try {
+ list.add(buildLocaleMap(locale, currentLocale));
+ } catch (FileNotFoundException e) {
+ log.warn("Can't show locale '" + locale + "': " + e);
+ }
+ }
-
- //get locales
- public List> getLocales(VitroRequest vreq) {
- List selectables = SelectedLocale.getSelectableLocales(vreq);
- if (selectables.isEmpty()) {
- return Collections.emptyList();
- }
- List> list = new ArrayList>();
- Locale currentLocale = SelectedLocale.getCurrentLocale(vreq);
- for (Locale locale : selectables) {
- try {
- list.add(buildLocaleMap(locale, currentLocale));
- } catch (FileNotFoundException e) {
- log.warn("Can't show the Locale selector for '" + locale
- + "': " + e);
- }
- }
-
- return list;
+ return list;
}
-
-
public HashMap getFullCodeToLanguageNameMap(List> localesList) {
HashMap codeToLanguageMap = new HashMap();
for(Map locale: localesList) {
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/controller/EditRequestDispatchController.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/controller/EditRequestDispatchController.java
index 4ed574347..16b1122fb 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/controller/EditRequestDispatchController.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/controller/EditRequestDispatchController.java
@@ -68,12 +68,15 @@ public class EditRequestDispatchController extends FreemarkerHttpServlet {
//TODO: Create this generator
final String RDFS_LABEL_FORM = "edu.cornell.mannlib.vitro.webapp.edit.n3editing.configuration.generators.RDFSLabelGenerator";
final String DEFAULT_DELETE_FORM = "edu.cornell.mannlib.vitro.webapp.edit.n3editing.configuration.generators.DefaultDeleteGenerator";
-
+ final String MANAGE_MENUS_FORM = "edu.cornell.mannlib.vitro.webapp.edit.n3editing.configuration.generators.ManagePageGenerator";
+
@Override
protected AuthorizationRequest requiredActions(VitroRequest vreq) {
// If request is for new individual, return simple do back end editing action permission
if (StringUtils.isNotEmpty(EditConfigurationUtils.getTypeOfNew(vreq))) {
return SimplePermission.DO_BACK_END_EDITING.ACTION;
+ } else if(MANAGE_MENUS_FORM.equals(vreq.getParameter("editForm"))) {
+ return SimplePermission.MANAGE_MENUS.ACTION;
}
// Check if this statement can be edited here and return unauthorized if not
String subjectUri = EditConfigurationUtils.getSubjectUri(vreq);
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/modelaccess/impl/RequestModelAccessImpl.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/modelaccess/impl/RequestModelAccessImpl.java
index 3f47566fe..63d70691c 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/modelaccess/impl/RequestModelAccessImpl.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/modelaccess/impl/RequestModelAccessImpl.java
@@ -202,7 +202,7 @@ public class RequestModelAccessImpl implements RequestModelAccess {
@Override
public OntModel getOntModel(String name, LanguageOption... options) {
- return addLanguageAwareness(getOntModel(new OntModelKey(name, options)));
+ return getOntModel(new OntModelKey(name, options));
}
private OntModel getOntModel(OntModelKey key) {
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/filter/FilteredResultSet.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/filter/FilteredResultSet.java
index 230766d3e..e116caece 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/filter/FilteredResultSet.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/filter/FilteredResultSet.java
@@ -9,6 +9,7 @@ import org.apache.jena.query.QuerySolution;
import org.apache.jena.query.ResultSet;
import org.apache.jena.rdf.model.Model;
import org.apache.jena.sparql.engine.binding.Binding;
+import org.apache.jena.sparql.engine.binding.BindingUtils;
public class FilteredResultSet implements ResultSet {
@@ -53,7 +54,7 @@ public class FilteredResultSet implements ResultSet {
@Override
public Binding nextBinding() {
- throw new UnsupportedOperationException("Can we ignore this?");
+ return BindingUtils.asBinding(nextSolution());
}
@Override
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/filter/LangSort.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/filter/LangSort.java
index 933da13c2..10007b119 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/filter/LangSort.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/filter/LangSort.java
@@ -36,7 +36,13 @@ public class LangSort {
}
protected int compareLangs(String t1lang, String t2lang) {
- return languageIndex(t1lang) - languageIndex(t2lang);
+ int index1 = languageIndex(t1lang);
+ int index2 = languageIndex(t2lang);
+ if(index1 == index2) {
+ return t1lang.compareTo(t2lang);
+ } else {
+ return languageIndex(t1lang) - languageIndex(t2lang);
+ }
}
/**
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/impl/jena/RDFServiceJena.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/impl/jena/RDFServiceJena.java
index 888a51506..09a98a647 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/impl/jena/RDFServiceJena.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/impl/jena/RDFServiceJena.java
@@ -201,8 +201,8 @@ public abstract class RDFServiceJena extends RDFServiceImpl implements RDFServic
log.debug("blank node model size " + blankNodeModel.size());
if (blankNodeModel.size() == 1) {
- log.warn("Deleting single triple with blank node: " + blankNodeModel);
- log.warn("This likely indicates a problem; excessive data may be deleted.");
+ log.debug("Deleting single triple with blank node: " + blankNodeModel);
+ log.debug("This could result in the deletion of multiple triples if multiple blank nodes match the same triple pattern.");
}
Query rootFinderQuery = QueryFactory.create(BNODE_ROOT_QUERY);
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/impl/jena/tdb/RDFServiceTDB.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/impl/jena/tdb/RDFServiceTDB.java
index 6b5e4557a..adcb69826 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/impl/jena/tdb/RDFServiceTDB.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/impl/jena/tdb/RDFServiceTDB.java
@@ -13,6 +13,8 @@ import java.nio.file.Paths;
import java.util.List;
import org.apache.jena.rdf.model.Model;
+import org.apache.jena.rdf.model.RDFNode;
+
import edu.cornell.mannlib.vitro.webapp.rdfservice.ResultSetConsumer;
import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
@@ -74,11 +76,19 @@ public class RDFServiceTDB extends RDFServiceJena {
}
notifyListenersOfPreChangeEvents(changeSet);
- dataset.begin(ReadWrite.WRITE);
- try {
- applyChangeSetToModel(changeSet, dataset);
- dataset.commit();
- } finally {
+ dataset.begin(ReadWrite.WRITE);
+ try {
+ boolean committed = false;
+ try {
+ applyChangeSetToModel(changeSet, dataset);
+ dataset.commit();
+ committed = true;
+ } finally {
+ if(!committed) {
+ dataset.abort();
+ }
+ }
+ } finally {
dataset.end();
}
@@ -93,6 +103,10 @@ public class RDFServiceTDB extends RDFServiceJena {
}
}
+ @Override
+ public boolean preferPreciseOptionals() {
+ return true;
+ }
@Override
public void close() {
@@ -232,6 +246,28 @@ public class RDFServiceTDB extends RDFServiceJena {
return isEquivalentGraph(graphURI, inStream, ModelSerializationFormat.NTRIPLE);
}
+ @Override
+ public long countTriples(RDFNode subject, RDFNode predicate, RDFNode object)
+ throws RDFServiceException {
+ dataset.begin(ReadWrite.READ);
+ try {
+ return super.countTriples(subject, predicate, object);
+ } finally {
+ dataset.end();
+ }
+ }
+
+ @Override
+ public Model getTriples(RDFNode subject, RDFNode predicate, RDFNode object,
+ long limit, long offset) throws RDFServiceException {
+ dataset.begin(ReadWrite.READ);
+ try {
+ return super.getTriples(subject, predicate, object, limit, offset);
+ } finally {
+ dataset.end();
+ }
+ }
+
/**
* Convert all of the references to integer compatible type to "integer" in the serialized graph.
*
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/utils/developer/DeveloperSettings.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/utils/developer/DeveloperSettings.java
index 09f97db46..b030db008 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/utils/developer/DeveloperSettings.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/utils/developer/DeveloperSettings.java
@@ -202,23 +202,18 @@ public class DeveloperSettings {
File dsFile = homeDir.resolve("config/developer.properties")
.toFile();
- if (!dsFile.exists()) {
- dsFile = homeDir.resolve("config/default.developer.properties").toFile();
- }
-
try (FileReader reader = new FileReader(dsFile)) {
Properties dsProps = new Properties();
dsProps.load(reader);
devSettings.updateFromProperties(dsProps);
log.info(devSettings);
- ss.info(this, "Loaded the '" + dsFile.getName() + "' file: "
+ ss.info(this, "Loaded the 'developer.properties' file: "
+ devSettings);
} catch (FileNotFoundException e) {
- ss.info(this, "Neither 'developer.properties' nor 'default.developer.properties' " +
- "files exist.");
+ ss.info(this, "'developer.properties' file does not exist.");
} catch (Exception e) {
ss.warning(this,
- "Failed to load the '" + dsFile.getAbsolutePath() + "' file.", e);
+ "Failed to load the 'developer.properties' file.", e);
}
}
diff --git a/api/src/main/java/org/vivoweb/linkeddatafragments/servlet/VitroLinkedDataFragmentServlet.java b/api/src/main/java/org/vivoweb/linkeddatafragments/servlet/VitroLinkedDataFragmentServlet.java
index 6e99778d0..e4427d82e 100644
--- a/api/src/main/java/org/vivoweb/linkeddatafragments/servlet/VitroLinkedDataFragmentServlet.java
+++ b/api/src/main/java/org/vivoweb/linkeddatafragments/servlet/VitroLinkedDataFragmentServlet.java
@@ -1,15 +1,24 @@
package org.vivoweb.linkeddatafragments.servlet;
-import com.fasterxml.jackson.databind.JsonNode;
-import edu.cornell.mannlib.vitro.webapp.beans.Ontology;
-import edu.cornell.mannlib.vitro.webapp.controller.VitroHttpServlet;
-import edu.cornell.mannlib.vitro.webapp.dao.OntologyDao;
-import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess;
-import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService;
-import edu.cornell.mannlib.vitro.webapp.config.ConfigurationProperties;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringReader;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map.Entry;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
import org.apache.commons.io.IOUtils;
-import org.apache.jena.riot.Lang;
import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.jena.riot.Lang;
import org.linkeddatafragments.config.ConfigReader;
import org.linkeddatafragments.datasource.DataSourceFactory;
import org.linkeddatafragments.datasource.DataSourceTypesRegistry;
@@ -22,26 +31,19 @@ import org.linkeddatafragments.fragments.ILinkedDataFragment;
import org.linkeddatafragments.fragments.ILinkedDataFragmentRequest;
import org.linkeddatafragments.util.MIMEParse;
import org.linkeddatafragments.views.ILinkedDataFragmentWriter;
-import org.vivoweb.linkeddatafragments.views.HtmlTriplePatternFragmentWriterImpl;
-import org.vivoweb.linkeddatafragments.views.LinkedDataFragmentWriterFactory;
import org.vivoweb.linkeddatafragments.datasource.rdfservice.RDFServiceBasedRequestProcessorForTPFs;
import org.vivoweb.linkeddatafragments.datasource.rdfservice.RDFServiceDataSourceType;
+import org.vivoweb.linkeddatafragments.views.HtmlTriplePatternFragmentWriterImpl;
+import org.vivoweb.linkeddatafragments.views.LinkedDataFragmentWriterFactory;
-import javax.servlet.ServletConfig;
-import javax.servlet.ServletContext;
-import javax.servlet.ServletException;
-import javax.servlet.annotation.WebServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.StringReader;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map.Entry;
+import com.fasterxml.jackson.databind.JsonNode;
+
+import edu.cornell.mannlib.vitro.webapp.beans.Ontology;
+import edu.cornell.mannlib.vitro.webapp.config.ConfigurationProperties;
+import edu.cornell.mannlib.vitro.webapp.controller.VitroHttpServlet;
+import edu.cornell.mannlib.vitro.webapp.dao.OntologyDao;
+import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess;
+import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService;
/**
* Servlet that responds with a Linked Data Fragment.
@@ -52,29 +54,13 @@ public class VitroLinkedDataFragmentServlet extends VitroHttpServlet {
private final static long serialVersionUID = 1L;
private static final String PROPERTY_TPF_ACTIVE_FLAG = "tpf.activeFlag";
+ private static final Log log = LogFactory.getLog(VitroLinkedDataFragmentServlet.class);
private ConfigReader config;
private final HashMap dataSources = new HashMap<>();
- private final Collection mimeTypes = new ArrayList<>();
private ConfigurationProperties configProps;
private String tpfActiveFlag;
- private File getConfigFile(ServletConfig config) throws IOException {
- String path = config.getServletContext().getRealPath("/");
- if (path == null) {
- // this can happen when running standalone
- path = System.getProperty("user.dir");
- }
- File cfg = new File(path, "config-example.json");
- if (!cfg.exists()) {
- throw new IOException("Configuration file " + cfg + " not found.");
- }
- if (!cfg.isFile()) {
- throw new IOException("Configuration file " + cfg + " is not a file.");
- }
- return cfg;
- }
-
@Override
public void init(ServletConfig servletConfig) throws ServletException {
try {
@@ -82,11 +68,11 @@ public class VitroLinkedDataFragmentServlet extends VitroHttpServlet {
configProps = ConfigurationProperties.getBean(ctx);
if (!configurationPresent()) {
- throw new ServletException("TPF is currently disabled. To enable, add 'tpfActive.flag=true' to the runtime.properties.");
- } else {
- if (!tpfActiveFlag.equalsIgnoreCase("true")) {
- throw new ServletException("TPF is currently disabled. To enable, set 'tpfActive.flag=true' in runtime.properties.");
- }
+ throw new ServletException("TPF is currently disabled. To enable, add '"
+ + PROPERTY_TPF_ACTIVE_FLAG + " = true' to runtime.properties.");
+ } else if (!tpfActiveFlag.equalsIgnoreCase("true")) {
+ throw new ServletException("TPF is currently disabled. To enable, set '"
+ + PROPERTY_TPF_ACTIVE_FLAG + " = true' in runtime.properties.");
}
RDFService rdfService = ModelAccess.on(ctx).getRDFService();
@@ -215,18 +201,18 @@ public class VitroLinkedDataFragmentServlet extends VitroHttpServlet {
writer.writeFragment(response.getOutputStream(), dataSource, fragment, ldfRequest);
} catch (DataSourceNotFoundException ex) {
+ log.error(ex, ex);
try {
response.setStatus(404);
writer.writeNotFound(response.getOutputStream(), request);
} catch (Exception ex1) {
+ log.error(ex1, ex1);
throw new ServletException(ex1);
}
- } catch (Exception e) {
- response.setStatus(500);
- writer.writeError(response.getOutputStream(), e);
- }
+ }
} catch (Exception e) {
+ log.error(e, e);
throw new ServletException(e);
}
finally {
diff --git a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dao/jena/RDFServiceGraphTest.java b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dao/jena/RDFServiceGraphTest.java
new file mode 100644
index 000000000..e5c8a65fa
--- /dev/null
+++ b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dao/jena/RDFServiceGraphTest.java
@@ -0,0 +1,62 @@
+package edu.cornell.mannlib.vitro.webapp.dao.jena;
+
+import static org.junit.Assert.assertEquals;
+
+import org.apache.jena.rdf.model.Model;
+import org.apache.jena.rdf.model.ModelFactory;
+import org.junit.Test;
+
+import edu.cornell.mannlib.vitro.testing.AbstractTestClass;
+import edu.cornell.mannlib.vitro.webapp.rdfservice.ChangeListener;
+import edu.cornell.mannlib.vitro.webapp.rdfservice.ModelChange;
+import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService;
+import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFServiceException;
+import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.jena.model.RDFServiceModel;
+
+
+public class RDFServiceGraphTest extends AbstractTestClass {
+
+ @Test
+ /**
+ * Test that creating a new model with the same underlying RDFServiceGraph
+ * does not result in a new listener registered on that graph. No matter
+ * how many models have been created using a given RDFServiceGraph, an event
+ * sent to the last-created model should be heard only once by the
+ * RDFService.
+ * @throws RDFServiceException
+ */
+ public void testEventListening() throws RDFServiceException {
+ Model m = ModelFactory.createDefaultModel();
+ RDFService rdfService = new RDFServiceModel(m);
+ EventsCounter counter = new EventsCounter();
+ rdfService.registerListener(counter);
+ RDFServiceGraph g = new RDFServiceGraph(rdfService);
+ Model model = null;
+ for (int i = 0; i < 100; i++) {
+ model = RDFServiceGraph.createRDFServiceModel(g);
+ }
+ model.notifyEvent("event");
+ assertEquals(1, counter.getCount());
+ }
+
+ private class EventsCounter implements ChangeListener {
+
+ private int count = 0;
+
+ public int getCount() {
+ return count;
+ }
+
+ @Override
+ public void notifyModelChange(ModelChange modelChange) {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void notifyEvent(String graphURI, Object event) {
+ count++;
+ }
+
+ }
+
+}
diff --git a/dependencies/pom.xml b/dependencies/pom.xml
index 591762fb7..078740a75 100644
--- a/dependencies/pom.xml
+++ b/dependencies/pom.xml
@@ -7,13 +7,13 @@
org.vivoweb
vitro-dependencies
- 1.11.2-SNAPSHOT
+ 1.12.0-SNAPSHOT
pom
org.vivoweb
vitro-project
- 1.11.2-SNAPSHOT
+ 1.12.0-SNAPSHOT
..
diff --git a/home/pom.xml b/home/pom.xml
index fdc0237f2..dc31e4e4f 100644
--- a/home/pom.xml
+++ b/home/pom.xml
@@ -7,13 +7,13 @@
org.vivoweb
vitro-home
- 1.11.2-SNAPSHOT
+ 1.12.0-SNAPSHOT
pom
org.vivoweb
vitro-project
- 1.11.2-SNAPSHOT
+ 1.12.0-SNAPSHOT
..
diff --git a/home/src/main/resources/config/default.applicationSetup.n3 b/home/src/main/resources/config/example.applicationSetup.n3
similarity index 87%
rename from home/src/main/resources/config/default.applicationSetup.n3
rename to home/src/main/resources/config/example.applicationSetup.n3
index 5d0562be2..0df7bdb6e 100644
--- a/home/src/main/resources/config/default.applicationSetup.n3
+++ b/home/src/main/resources/config/example.applicationSetup.n3
@@ -26,7 +26,7 @@
:hasSearchIndexer :basicSearchIndexer ;
:hasImageProcessor :iioImageProcessor ;
:hasFileStorage :ptiFileStorage ;
- :hasContentTripleSource :sdbContentTripleSource ;
+ :hasContentTripleSource :tdbContentTripleSource ;
:hasConfigurationTripleSource :tdbConfigurationTripleSource ;
:hasTBoxReasonerModule :jfactTBoxReasonerModule .
@@ -82,22 +82,22 @@
# ----------------------------
#
# Content triples source module: holds data contents
-# The SDB-based implementation is the default option. It reads its parameters
+# The TDB-based implementation is the default option. It reads its parameters
# from the runtime.properties file, for backward compatibility.
#
-# Other implementations are based on a local TDB instance, a "standard" SPARQL
+# Other implementations are based on an SDB instance, a "standard" SPARQL
# endpoint, or a Virtuoso endpoint, with parameters as shown.
#
-:sdbContentTripleSource
- a vitroWebapp:triplesource.impl.sdb.ContentTripleSourceSDB ,
- vitroWebapp:modules.tripleSource.ContentTripleSource .
+#:sdbContentTripleSource
+# a vitroWebapp:triplesource.impl.sdb.ContentTripleSourceSDB ,
+# vitroWebapp:modules.tripleSource.ContentTripleSource .
-#:tdbContentTripleSource
-# a vitroWebapp:triplesource.impl.tdb.ContentTripleSourceTDB ,
-# vitroWebapp:modules.tripleSource.ContentTripleSource ;
-# # May be an absolute path, or relative to the Vitro home directory.
-# :hasTdbDirectory "tdbContentModels" .
+:tdbContentTripleSource
+ a vitroWebapp:triplesource.impl.tdb.ContentTripleSourceTDB ,
+ vitroWebapp:modules.tripleSource.ContentTripleSource ;
+ # May be an absolute path, or relative to the Vitro home directory.
+ :hasTdbDirectory "tdbContentModels" .
#:sparqlContentTripleSource
# a vitroWebapp:triplesource.impl.virtuoso.ContentTripleSourceSPARQL ,
diff --git a/home/src/main/resources/config/default.developer.properties b/home/src/main/resources/config/example.developer.properties
similarity index 100%
rename from home/src/main/resources/config/default.developer.properties
rename to home/src/main/resources/config/example.developer.properties
diff --git a/home/src/main/resources/config/default.runtime.properties b/home/src/main/resources/config/example.runtime.properties
similarity index 57%
rename from home/src/main/resources/config/default.runtime.properties
rename to home/src/main/resources/config/example.runtime.properties
index 4d046873e..8e287ca0e 100644
--- a/home/src/main/resources/config/default.runtime.properties
+++ b/home/src/main/resources/config/example.runtime.properties
@@ -9,53 +9,92 @@
#
# -----------------------------------------------------------------------------
+#
+# This namespace will be used when generating URIs for objects created in the
+# editor. In order to serve linked data, the default namespace must be composed
+# as follows (optional elements in parentheses):
+#
+# scheme + server_name (+ port) (+ servlet_context) + "/individual/"
+#
+# For example, Cornell's default namespace is:
+#
+# http://vivo.cornell.edu/individual/
+#
+Vitro.defaultNamespace = http://vivo.mydomain.edu/individual/
+
#
# URL of Solr context used in local Vitro search. This will usually consist of:
-# scheme + server_name + port + vitro_webapp_name + "solr"
-# In the standard installation, the Solr context will be on the same server as Vitro,
-# and in the same Tomcat instance. The path will be the Vitro webapp.name (specified
-# above) + "solr"
+# scheme + server_name + port + "solr" + solr_core_name
+# In a standard Solr installation, the Solr service will be available on port
+# 8983. The path will be /solr followed by the name used when adding a core
+# for Vitro.
# Example:
-# vitro.local.solr.url = http://localhost:8080/vitrosolr
-vitro.local.solr.url = http://localhost:8080/vitrosolr
+# vitro.local.solr.url = http://localhost:8983/solr/vitrocore
+#
+vitro.local.solr.url = http://localhost:8983/solr/vitrocore
#
# Email parameters which VIVO can use to send mail. If these are left empty,
# the "Contact Us" form will be disabled and users will not be notified of
# changes to their accounts.
-#
-email.smtpHost = smtp.my.domain.edu
-email.replyTo = vivoAdmin@my.domain.edu
+ # Example:
+ # email.smtpHost = smtp.mydomain.edu
+ # email.replyTo = vitroAdmin@mydomain.edu
+ #
+email.smtpHost =
+email.replyTo =
#
-# The basic parameters for a MySQL database connection. Change the end of the
-# URL to reflect your database name (if it is not "vitro"). Change the username
-# and password to match the authorized user you created in MySQL.
+# NOTE: VitroConnection.DataSource.* properties are only used in conjuction with
+# an SDB triple store.
#
-VitroConnection.DataSource.url = jdbc:mysql://localhost/vitro
-VitroConnection.DataSource.username = vitroweb
-VitroConnection.DataSource.password = vitrovitro
+# The basic parameters for a database connection. Change the end of the
+# URL to reflect your database name (if it is not "vitrodb"). Change the username
+# and password to match the authorized database user you created.
+#
+# VitroConnection.DataSource.url = jdbc:mysql://localhost/vitrodb
+# VitroConnection.DataSource.username = vitrodbUsername
+# VitroConnection.DataSource.password = vitrodbPassword
#
# The maximum number of active connections in the database connection pool.
# Increase this value to support a greater number of concurrent page requests.
#
-VitroConnection.DataSource.pool.maxActive = 40
+# VitroConnection.DataSource.pool.maxActive = 40
#
# The maximum number of database connections that will be allowed
# to remain idle in the connection pool. Default is 25%
# of the maximum number of active connections.
#
-VitroConnection.DataSource.pool.maxIdle = 10
+# VitroConnection.DataSource.pool.maxIdle = 10
#
-# Parameters to change in order to use VIVO with a database other than
+# Parameters to change in order to use Vitro with a database other than
# MySQL.
#
-VitroConnection.DataSource.dbtype = MySQL
-VitroConnection.DataSource.driver = com.mysql.jdbc.Driver
-VitroConnection.DataSource.validationQuery = SELECT 1
+# VitroConnection.DataSource.dbtype = MySQL
+# VitroConnection.DataSource.driver = com.mysql.jdbc.Driver
+# VitroConnection.DataSource.validationQuery = SELECT 1
+
+#
+# Include sections between
+# tags when executing 'list view' queries that retrieve data
+# for property lists on profile pages.
+#
+# Including these optional sections does not change the query
+# semantics, but may improve performance.
+#
+# Default is true if not set.
+#
+# listview.usePreciseSubquery = true
+
+#
+# The email address of the root user for the VIVO application. The password
+# for this user is initially set to "rootPassword", but you will be asked to
+# change the password the first time you log in.
+#
+rootUser.emailAddress = root@myDomain.com
#
# Argon2 password hashing parameters for time, memory and parallelism required to
@@ -134,7 +173,15 @@ proxy.eligibleTypeList = http://www.w3.org/2002/07/owl#Thing
#
# languages.selectableLocales = en, es, fr
-# Triple pattern fragments is a very fast, very simple means for querying a triple store.
-# The triple pattern fragments API in VIVO puts little load on the server, providing a simple means for getting data from the triple store. The API has a web interface for manual use, can be used from the command line via curl, and can be used by programs.
-
+# Triple Pattern Fragments is a very fast, very simple means for querying a
+# triple store. The Triple Pattern Fragments API in VIVO puts little load on
+# the server, providing a simple means for getting data from the triple store.
+# The API has a web interface for manual use, can be used from the command line
+# via curl, and can be used by programs.
+#
+# Vitro's Triple Pattern Fragments API does not require authentication and
+# makes the full RDF graph available regardless of display or publish levels
+# set on particular properties. Enable Triple Pattern Fragments only if your
+# Vitro does not contain restricted data that should not be shared with others.
+#
# tpf.activeFlag = true
diff --git a/installer/example-settings.xml b/installer/example-settings.xml
new file mode 100644
index 000000000..39755cb7d
--- /dev/null
+++ b/installer/example-settings.xml
@@ -0,0 +1,21 @@
+
+
+
+
+ defaults
+
+ vitro
+
+ /usr/local/vitro/home
+ /usr/local/tomcat
+
+ vitro
+
+
+
+
+
+ defaults
+
+
diff --git a/installer/home/pom.xml b/installer/home/pom.xml
index be8bb3023..83f2dc8fe 100644
--- a/installer/home/pom.xml
+++ b/installer/home/pom.xml
@@ -7,13 +7,13 @@
org.vivoweb
vitro-installer-home
- 1.11.2-SNAPSHOT
+ 1.12.0-SNAPSHOT
pom
org.vivoweb
vitro-installer
- 1.11.2-SNAPSHOT
+ 1.12.0-SNAPSHOT
..
diff --git a/installer/pom.xml b/installer/pom.xml
index a79aaa4c9..68dd1dbd7 100644
--- a/installer/pom.xml
+++ b/installer/pom.xml
@@ -7,13 +7,13 @@
org.vivoweb
vitro-installer
- 1.11.2-SNAPSHOT
+ 1.12.0-SNAPSHOT
pom
org.vivoweb
vitro-project
- 1.11.2-SNAPSHOT
+ 1.12.0-SNAPSHOT
..
diff --git a/installer/solr/pom.xml b/installer/solr/pom.xml
index ae67209fd..58903929a 100644
--- a/installer/solr/pom.xml
+++ b/installer/solr/pom.xml
@@ -7,13 +7,13 @@
org.vivoweb
vitro-installer-solr
- 1.11.0-SNAPSHOT
+ 1.12.0-SNAPSHOT
war
org.vivoweb
vitro-installer
- 1.11.0-SNAPSHOT
+ 1.12.0-SNAPSHOT
..
diff --git a/installer/webapp/pom.xml b/installer/webapp/pom.xml
index 4be7c1604..69abcc2a8 100644
--- a/installer/webapp/pom.xml
+++ b/installer/webapp/pom.xml
@@ -7,13 +7,13 @@
org.vivoweb
vitro-installer-webapp
- 1.11.2-SNAPSHOT
+ 1.12.0-SNAPSHOT
war
org.vivoweb
vitro-installer
- 1.11.2-SNAPSHOT
+ 1.12.0-SNAPSHOT
..
diff --git a/installer/webapp/src/main/webResources/META-INF/context.xml b/installer/webapp/src/main/webResources/META-INF/context.xml
index ac110751c..53ea22ae5 100644
--- a/installer/webapp/src/main/webResources/META-INF/context.xml
+++ b/installer/webapp/src/main/webResources/META-INF/context.xml
@@ -1,51 +1,8 @@
-
-
-
-
-
-
-
-
-
-
-
+ value="${vitro-dir}" override="true"/>
diff --git a/pom.xml b/pom.xml
index 13d2c8f1b..c0775cf69 100644
--- a/pom.xml
+++ b/pom.xml
@@ -7,7 +7,7 @@
org.vivoweb
vitro-project
- 1.11.2-SNAPSHOT
+ 1.12.0-SNAPSHOT
pom
Vitro
diff --git a/webapp/pom.xml b/webapp/pom.xml
index a9b1a8fe5..4e66b0998 100644
--- a/webapp/pom.xml
+++ b/webapp/pom.xml
@@ -7,13 +7,13 @@
org.vivoweb
vitro-webapp
- 1.11.2-SNAPSHOT
+ 1.12.0-SNAPSHOT
war
org.vivoweb
vitro-project
- 1.11.2-SNAPSHOT
+ 1.12.0-SNAPSHOT
..
@@ -41,7 +41,7 @@
org.vivoweb
vitro-api
- 1.11.2-SNAPSHOT
+ 1.12.0-SNAPSHOT
diff --git a/webapp/src/main/webapp/templates/freemarker/body/accounts/userAccounts-createPassword.ftl b/webapp/src/main/webapp/templates/freemarker/body/accounts/userAccounts-createPassword.ftl
index a3ba4273c..126a2626f 100644
--- a/webapp/src/main/webapp/templates/freemarker/body/accounts/userAccounts-createPassword.ftl
+++ b/webapp/src/main/webapp/templates/freemarker/body/accounts/userAccounts-createPassword.ftl
@@ -26,7 +26,7 @@