Merge branch 'rel-1.12.0-RC' into main

This commit is contained in:
Ralph O'Flinn 2021-07-21 07:20:21 -05:00
commit 5a4648554a
57 changed files with 620 additions and 689 deletions

View file

@ -7,13 +7,13 @@
<groupId>org.vivoweb</groupId> <groupId>org.vivoweb</groupId>
<artifactId>vitro-api</artifactId> <artifactId>vitro-api</artifactId>
<version>1.11.2-SNAPSHOT</version> <version>1.12.0-SNAPSHOT</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<parent> <parent>
<groupId>org.vivoweb</groupId> <groupId>org.vivoweb</groupId>
<artifactId>vitro-project</artifactId> <artifactId>vitro-project</artifactId>
<version>1.11.2-SNAPSHOT</version> <version>1.12.0-SNAPSHOT</version>
<relativePath>..</relativePath> <relativePath>..</relativePath>
</parent> </parent>
@ -66,7 +66,7 @@
<dependency> <dependency>
<groupId>org.vivoweb</groupId> <groupId>org.vivoweb</groupId>
<artifactId>vitro-dependencies</artifactId> <artifactId>vitro-dependencies</artifactId>
<version>1.11.2-SNAPSHOT</version> <version>1.12.0-SNAPSHOT</version>
<type>pom</type> <type>pom</type>
</dependency> </dependency>
<dependency> <dependency>

View file

@ -24,7 +24,6 @@ import edu.cornell.mannlib.vitro.webapp.utils.jena.criticalsection.LockableModel
*/ */
public class ApplicationSetup implements ServletContextListener { public class ApplicationSetup implements ServletContextListener {
private static final String APPLICATION_SETUP_PATH = "config/applicationSetup.n3"; 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 ServletContext ctx;
private StartupStatus ss; private StartupStatus ss;
@ -46,8 +45,6 @@ public class ApplicationSetup implements ServletContextListener {
this.vitroHomeDir = VitroHomeDirectory.find(ctx); this.vitroHomeDir = VitroHomeDirectory.find(ctx);
ss.info(this, vitroHomeDir.getDiscoveryMessage()); ss.info(this, vitroHomeDir.getDiscoveryMessage());
this.vitroHomeDir.populate();
locateApplicationConfigFile(); locateApplicationConfigFile();
loadApplicationConfigFile(); loadApplicationConfigFile();
createConfigurationBeanLoader(); createConfigurationBeanLoader();
@ -66,19 +63,11 @@ public class ApplicationSetup implements ServletContextListener {
private void locateApplicationConfigFile() { private void locateApplicationConfigFile() {
Path path = this.vitroHomeDir.getPath().resolve(APPLICATION_SETUP_PATH); 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)) { if (!Files.exists(path)) {
throw new IllegalStateException("Neither '" + APPLICATION_SETUP_PATH + "' nor '" + throw new IllegalStateException("'" + path + "' does not exist.");
APPLICATION_SETUP_DEFAULT_PATH + "' were found in " +
this.vitroHomeDir.getPath());
} }
if (!Files.isReadable(path)) { if (!Files.isReadable(path)) {
throw new IllegalStateException("No readable '" + APPLICATION_SETUP_PATH + "' nor '" + throw new IllegalStateException("Can't read '" + path + "'");
APPLICATION_SETUP_DEFAULT_PATH + "' files were found in " +
this.vitroHomeDir.getPath());
} }
this.configFile = path; this.configFile = path;
} }

View file

@ -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 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.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.naming.InitialContext;
import javax.servlet.ServletContext; 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.Log;
import org.apache.commons.logging.LogFactory; 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 { public class VitroHomeDirectory {
private static final Log log = LogFactory.getLog(VitroHomeDirectory.class); 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) { public static VitroHomeDirectory find(ServletContext ctx) {
HomeDirectoryFinder finder = new HomeDirectoryFinder(ctx); HomeDirectoryFinder finder = new HomeDirectoryFinder(ctx);
return new VitroHomeDirectory(ctx, finder.getPath(), return new VitroHomeDirectory(ctx, finder.getPath(),
@ -73,219 +52,6 @@ public class VitroHomeDirectory {
return discoveryMessage; 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<String, String> 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<String, String> untar(File destination) {
log.info("Syncing VIVO home at: " + destination.getPath());
Map<String, String> digest = new HashMap<>();
Map<String, String> 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<String, String> 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; `<checksum> *<file>`.
*
* @param digest checksum digest to write
*/
private void writeDigest(Map<String, String> digest) {
File storedDigest = new File(getPath().toFile(), DIGEST_FILE_NAME);
try (
FileOutputStream fos = new FileOutputStream(storedDigest);
OutputStreamWriter osw = new OutputStreamWriter(fos);
) {
for (Map.Entry<String, String> 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 `<checksum> *<file>`
* @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. * Find something that specifies the location of the Vitro home directory.
* Look in the JDNI environment, the system properties, and the * Look in the JDNI environment, the system properties, and the
@ -326,13 +92,24 @@ public class VitroHomeDirectory {
} }
public void getVhdFromJndi() { public void getVhdFromJndi() {
String vhdPath = ContextProperties.findJndiProperty(VHD_JNDI_PATH); try {
log.debug("'" + VHD_JNDI_PATH + "' as specified by JNDI: " + vhdPath); 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( String message = String.format(
"JNDI environment '%s' was set to '%s'", "JNDI environment '%s' was set to '%s'",
VHD_JNDI_PATH, vhdPath); VHD_JNDI_PATH, vhdPath);
foundLocations.add(new Found(Paths.get(vhdPath), message)); foundLocations.add(new Found(Paths.get(vhdPath), message));
} }
} catch (Exception e) {
log.debug("JNDI lookup failed. " + e);
}
}
private void getVhdFromSystemProperties() { private void getVhdFromSystemProperties() {
String vhdPath = System.getProperty(VHD_SYSTEM_PROPERTY); String vhdPath = System.getProperty(VHD_SYSTEM_PROPERTY);

View file

@ -7,17 +7,11 @@ import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; 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. * 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 class UserAccount {
public static final int MIN_PASSWORD_LENGTH = 6; public static final int MIN_PASSWORD_LENGTH = 6;
@ -52,6 +46,7 @@ public class UserAccount {
private String md5Password = ""; // Never null. private String md5Password = ""; // Never null.
private String oldPassword = ""; // Never null. private String oldPassword = ""; // Never null.
private long passwordLinkExpires = 0L; // Never negative. private long passwordLinkExpires = 0L; // Never negative.
private String emailKey = "";
private boolean passwordChangeRequired = false; private boolean passwordChangeRequired = false;
private int loginCount = 0; // Never negative. private int loginCount = 0; // Never negative.
@ -133,15 +128,27 @@ public class UserAccount {
return passwordLinkExpires; return passwordLinkExpires;
} }
public String getPasswordLinkExpiresHash() {
return limitStringLength(8, Authenticator.applyArgon2iEncoding(String
.valueOf(passwordLinkExpires)));
}
public void setPasswordLinkExpires(long passwordLinkExpires) { public void setPasswordLinkExpires(long passwordLinkExpires) {
this.passwordLinkExpires = Math.max(0, 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() { public boolean isPasswordChangeRequired() {
return passwordChangeRequired; return passwordChangeRequired;
} }
@ -247,6 +254,7 @@ public class UserAccount {
+ (", oldPassword=" + oldPassword) + (", oldPassword=" + oldPassword)
+ (", argon2password=" + argon2Password) + (", argon2password=" + argon2Password)
+ (", passwordLinkExpires=" + passwordLinkExpires) + (", passwordLinkExpires=" + passwordLinkExpires)
+ (", emailKey =" + emailKey)
+ (", passwordChangeRequired=" + passwordChangeRequired) + (", passwordChangeRequired=" + passwordChangeRequired)
+ (", externalAuthOnly=" + externalAuthOnly) + (", externalAuthOnly=" + externalAuthOnly)
+ (", loginCount=" + loginCount) + (", status=" + status) + (", loginCount=" + loginCount) + (", status=" + status)

View file

@ -109,7 +109,7 @@ public abstract class ConfigurationProperties {
throw new NullPointerException("bean may not be null."); throw new NullPointerException("bean may not be null.");
} }
context.setAttribute(ATTRIBUTE_NAME, bean); context.setAttribute(ATTRIBUTE_NAME, bean);
log.info(bean); log.debug(bean);
} }
/** Package access, so unit tests can call it. */ /** Package access, so unit tests can call it. */

View file

@ -32,10 +32,8 @@ public class ConfigurationPropertiesImpl extends ConfigurationProperties {
public ConfigurationPropertiesImpl(InputStream stream, public ConfigurationPropertiesImpl(InputStream stream,
Map<String, String> preemptiveProperties, Map<String, String> preemptiveProperties,
Map<String, String> buildProperties, Map<String, String> buildProperties) throws IOException {
Map<String, String> contextProperties) throws IOException {
Map<String, String> map = new HashMap<>(buildProperties); Map<String, String> map = new HashMap<>(buildProperties);
map.putAll(contextProperties);
Properties props = loadFromPropertiesFile(stream); Properties props = loadFromPropertiesFile(stream);
for (String key: props.stringPropertyNames()) { for (String key: props.stringPropertyNames()) {

View file

@ -49,12 +49,12 @@ public class ConfigurationPropertiesSetup implements ServletContextListener {
/** Name of the file that contains runtime properties. */ /** Name of the file that contains runtime properties. */
private static final String FILE_RUNTIME_PROPERTIES = "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 */ /** Configuration property to store the Vitro home directory */
private static final String VHD_CONFIGURATION_PROPERTY = "vitro.home"; 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 @Override
public void contextInitialized(ServletContextEvent sce) { public void contextInitialized(ServletContextEvent sce) {
ServletContext ctx = sce.getServletContext(); ServletContext ctx = sce.getServletContext();
@ -69,17 +69,18 @@ public class ConfigurationPropertiesSetup implements ServletContextListener {
File vitroHomeDirConfig = new File(vitroHomeDir.getPath() File vitroHomeDirConfig = new File(vitroHomeDir.getPath()
.concat(File.separator).concat("config")); .concat(File.separator).concat("config"));
String rpfLocation = findMultipleRuntimePropertiesFiles(
vitroHomeDir, vitroHomeDirConfig);
File runtimePropertiesFile = locateRuntimePropertiesFile( File runtimePropertiesFile = locateRuntimePropertiesFile(
vitroHomeDirConfig, ss); vitroHomeDir, vitroHomeDirConfig, ss);
stream = new FileInputStream(runtimePropertiesFile); stream = new FileInputStream(runtimePropertiesFile);
Map<String, String> preempts = createPreemptiveProperties( Map<String, String> preempts = createPreemptiveProperties(
VHD_CONFIGURATION_PROPERTY, vitroHomeDir); VHD_CONFIGURATION_PROPERTY, vitroHomeDir, RP_MULTIPLE, rpfLocation);
ConfigurationPropertiesImpl bean = new ConfigurationPropertiesImpl( ConfigurationPropertiesImpl bean = new ConfigurationPropertiesImpl(
stream, preempts, stream, preempts, new BuildProperties(ctx).getMap());
new BuildProperties(ctx).getMap(),
new ContextProperties().getMap());
ConfigurationProperties.setBean(ctx, bean); ConfigurationProperties.setBean(ctx, bean);
ss.info(this, "Loaded " + bean.getPropertyMap().size() 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(vitroHomeDir, FILE_RUNTIME_PROPERTIES);
File rpf = new File(vitroHomeDirConfig, FILE_RUNTIME_PROPERTIES); File rpfc = new File(vitroHomeDirConfig, FILE_RUNTIME_PROPERTIES);
// Have we found a suitable runtime.properties file? if (rpf.exists() && !rpfc.exists()) {
if (!rpf.exists() || !rpf.isFile() || !rpf.canRead()) { return "home";
} else if (rpf.exists() && rpfc.exists()) {
// If not... look for the default runtime.properties return "both";
rpf = new File(vitroHomeDirConfig, FILE_DEFAULT_RUNTIME_PROPERTIES); } 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 (!rpf.exists() || !rpf.isFile()) { private File locateRuntimePropertiesFile(File vitroHomeDir,
throw new IllegalStateException("Neither '" + FILE_RUNTIME_PROPERTIES + "' nor '" + File vitroHomeDirConfig, StartupStatus ss) {
FILE_DEFAULT_RUNTIME_PROPERTIES + "' were found in " +
vitroHomeDirConfig.getAbsolutePath()); File rpf = new File(vitroHomeDir, FILE_RUNTIME_PROPERTIES);
File rpfc = new File(vitroHomeDirConfig, FILE_RUNTIME_PROPERTIES);
if (!rpf.exists()) {
rpf = rpfc;
}
if (!rpf.isFile()) {
throw new IllegalStateException("'" + rpf.getPath()
+ "' is not a file.");
} }
if (!rpf.canRead()) { if (!rpf.canRead()) {
throw new IllegalStateException("No readable '" + FILE_RUNTIME_PROPERTIES + "' nor '" + throw new IllegalStateException("Cannot read '" + rpf.getPath()
FILE_DEFAULT_RUNTIME_PROPERTIES + "' files were found in " + + "'.");
vitroHomeDirConfig.getAbsolutePath());
} }
ss.info(this, "Loading runtime properties from '" + rpf.getPath() + "'"); ss.info(this, "Loading runtime properties from '" + rpf.getPath() + "'");
return rpf; return rpf;
} }
private Map<String, String> createPreemptiveProperties( private Map<String, String> createPreemptiveProperties(
String propertyVitroHome, File vitroHomeDir) { String propertyVitroHome, File vitroHomeDir, String propertyRpfMultiple,
String rpfLocation) {
Map<String, String> map = new HashMap<String, String>(); Map<String, String> map = new HashMap<String, String>();
map.put(propertyVitroHome, vitroHomeDir.getAbsolutePath()); map.put(propertyVitroHome, vitroHomeDir.getAbsolutePath());
map.put(propertyRpfMultiple, rpfLocation);
return map; return map;
} }

View file

@ -48,6 +48,7 @@ public class ConfigurationPropertiesSmokeTests implements
StartupStatus ss = StartupStatus.getBean(ctx); StartupStatus ss = StartupStatus.getBean(ctx);
checkDefaultNamespace(ctx, props, ss); checkDefaultNamespace(ctx, props, ss);
checkMultipleRPFs(ctx, props, ss);
checkLanguages(ctx, props, ss); checkLanguages(ctx, props, ss);
checkEncryptionParameters(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: * Warn if we set up the languages incorrectly:
* *

View file

@ -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<String, String> propertyMap;
public ContextProperties() {
Map<String, String> 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<String, String> getMap() {
return this.propertyMap;
}
}

View file

@ -249,6 +249,7 @@ public class UserAccountsSelector {
user.setMd5Password(ifLiteralPresent(solution, "md5pwd", "")); user.setMd5Password(ifLiteralPresent(solution, "md5pwd", ""));
user.setArgon2Password(ifLiteralPresent(solution, "a2pwd", "")); user.setArgon2Password(ifLiteralPresent(solution, "a2pwd", ""));
user.setPasswordLinkExpires(ifLongPresent(solution, "expire", 0L)); user.setPasswordLinkExpires(ifLongPresent(solution, "expire", 0L));
user.setEmailKey(ifLiteralPresent(solution, "emailKey", ""));
user.setLoginCount(ifIntPresent(solution, "count", 0)); user.setLoginCount(ifIntPresent(solution, "count", 0));
user.setLastLoginTime(ifLongPresent(solution, "lastLogin", 0)); user.setLastLoginTime(ifLongPresent(solution, "lastLogin", 0));
user.setStatus(parseStatus(solution, "status", null)); user.setStatus(parseStatus(solution, "status", null));

View file

@ -156,6 +156,7 @@ public class UserAccountsAddPage extends UserAccountsPage {
u.setOldPassword(""); u.setOldPassword("");
u.setPasswordChangeRequired(false); u.setPasswordChangeRequired(false);
u.setPasswordLinkExpires(0); u.setPasswordLinkExpires(0);
u.setEmailKey("");
u.setLoginCount(0); u.setLoginCount(0);
u.setLastLoginTime(0L); u.setLastLoginTime(0L);
u.setStatus(Status.INACTIVE); u.setStatus(Status.INACTIVE);

View file

@ -84,6 +84,7 @@ public abstract class UserAccountsAddPageStrategy extends UserAccountsPage {
u.setStatus(Status.ACTIVE); u.setStatus(Status.ACTIVE);
} else { } else {
u.setPasswordLinkExpires(figureExpirationDate().getTime()); u.setPasswordLinkExpires(figureExpirationDate().getTime());
u.generateEmailKey();
u.setStatus(Status.INACTIVE); u.setStatus(Status.INACTIVE);
} }
} }
@ -119,10 +120,8 @@ public abstract class UserAccountsAddPageStrategy extends UserAccountsPage {
private String buildCreatePasswordLink() { private String buildCreatePasswordLink() {
try { try {
String email = page.getAddedAccount().getEmailAddress(); String email = page.getAddedAccount().getEmailAddress();
String hash = page.getAddedAccount() String key = page.getAddedAccount().getEmailKey();
.getPasswordLinkExpiresHash(); String relativeUrl = UrlBuilder.getUrl(CREATE_PASSWORD_URL, "user", email, "key", key);
String relativeUrl = UrlBuilder.getUrl(CREATE_PASSWORD_URL,
"user", email, "key", hash);
URL context = new URL(vreq.getRequestURL().toString()); URL context = new URL(vreq.getRequestURL().toString());
URL url = new URL(context, relativeUrl); URL url = new URL(context, relativeUrl);

View file

@ -274,6 +274,7 @@ public class UserAccountsEditPage extends UserAccountsPage {
userAccount.setOldPassword(""); userAccount.setOldPassword("");
userAccount.setPasswordChangeRequired(false); userAccount.setPasswordChangeRequired(false);
userAccount.setPasswordLinkExpires(0L); userAccount.setPasswordLinkExpires(0L);
userAccount.setEmailKey("");
} }
if (isRootUser()) { if (isRootUser()) {

View file

@ -82,6 +82,7 @@ public abstract class UserAccountsEditPageStrategy extends UserAccountsPage {
protected void setAdditionalProperties(UserAccount u) { protected void setAdditionalProperties(UserAccount u) {
if (resetPassword && !page.isExternalAuthOnly()) { if (resetPassword && !page.isExternalAuthOnly()) {
u.setPasswordLinkExpires(figureExpirationDate().getTime()); u.setPasswordLinkExpires(figureExpirationDate().getTime());
u.generateEmailKey();
} }
} }
@ -121,10 +122,8 @@ public abstract class UserAccountsEditPageStrategy extends UserAccountsPage {
private String buildResetPasswordLink() { private String buildResetPasswordLink() {
try { try {
String email = page.getUpdatedAccount().getEmailAddress(); String email = page.getUpdatedAccount().getEmailAddress();
String hash = page.getUpdatedAccount() String key = page.getUpdatedAccount().getEmailKey();
.getPasswordLinkExpiresHash(); String relativeUrl = UrlBuilder.getUrl(RESET_PASSWORD_URL, "user", email, "key", key);
String relativeUrl = UrlBuilder.getUrl(RESET_PASSWORD_URL,
"user", email, "key", hash);
URL context = new URL(vreq.getRequestURL().toString()); URL context = new URL(vreq.getRequestURL().toString());
URL url = new URL(context, relativeUrl); URL url = new URL(context, relativeUrl);

View file

@ -36,6 +36,7 @@ public class UserAccountsCreatePasswordPage extends
userAccount.setArgon2Password(Authenticator.applyArgon2iEncoding(newPassword)); userAccount.setArgon2Password(Authenticator.applyArgon2iEncoding(newPassword));
userAccount.setMd5Password(""); userAccount.setMd5Password("");
userAccount.setPasswordLinkExpires(0L); userAccount.setPasswordLinkExpires(0L);
userAccount.setEmailKey("");
userAccount.setPasswordChangeRequired(false); userAccount.setPasswordChangeRequired(false);
userAccount.setStatus(Status.ACTIVE); userAccount.setStatus(Status.ACTIVE);
userAccountsDao.updateUserAccount(userAccount); userAccountsDao.updateUserAccount(userAccount);
@ -54,6 +55,11 @@ public class UserAccountsCreatePasswordPage extends
return i18n.text("account_already_activated", userEmail); return i18n.text("account_already_activated", userEmail);
} }
@Override
protected String passwordChangeInavlidKeyMessage() {
return i18n.text("password_change_invalid_key", userEmail);
}
@Override @Override
protected String templateName() { protected String templateName() {
return TEMPLATE_NAME; return TEMPLATE_NAME;

View file

@ -195,6 +195,7 @@ public class UserAccountsFirstTimeExternalPage extends UserAccountsPage {
u.setExternalAuthId(externalAuthId); u.setExternalAuthId(externalAuthId);
u.setPasswordChangeRequired(false); u.setPasswordChangeRequired(false);
u.setPasswordLinkExpires(0); u.setPasswordLinkExpires(0);
u.setEmailKey("");
u.setExternalAuthOnly(true); u.setExternalAuthOnly(true);
u.setLoginCount(0); u.setLoginCount(0);
u.setStatus(Status.ACTIVE); u.setStatus(Status.ACTIVE);

View file

@ -159,6 +159,7 @@ public abstract class UserAccountsMyAccountPageStrategy extends
userAccount.setMd5Password(""); userAccount.setMd5Password("");
userAccount.setPasswordChangeRequired(false); userAccount.setPasswordChangeRequired(false);
userAccount.setPasswordLinkExpires(0L); userAccount.setPasswordLinkExpires(0L);
userAccount.setEmailKey("");
} }
} }

View file

@ -103,12 +103,12 @@ public abstract class UserAccountsPasswordBasePage extends UserAccountsPage {
return; return;
} }
String expectedKey = userAccount.getPasswordLinkExpiresHash(); String expectedKey = userAccount.getEmailKey();
if (!key.equals(expectedKey)) { if (key.isEmpty() || !key.equals(expectedKey)) {
log.warn("Password request for '" + userEmail + "' is bogus: key (" log.warn("Password request for '" + userEmail + "' is bogus: key ("
+ key + ") doesn't match expected key (" + expectedKey + key + ") doesn't match expected key (" + expectedKey
+ ")"); + ")");
bogusMessage = passwordChangeNotPendingMessage(); bogusMessage = passwordChangeInavlidKeyMessage();
return; return;
} }
@ -153,7 +153,7 @@ public abstract class UserAccountsPasswordBasePage extends UserAccountsPage {
body.put("minimumLength", UserAccount.MIN_PASSWORD_LENGTH); body.put("minimumLength", UserAccount.MIN_PASSWORD_LENGTH);
body.put("maximumLength", UserAccount.MAX_PASSWORD_LENGTH); body.put("maximumLength", UserAccount.MAX_PASSWORD_LENGTH);
body.put("userAccount", userAccount); body.put("userAccount", userAccount);
body.put("key", userAccount.getPasswordLinkExpiresHash()); body.put("key", userAccount.getEmailKey());
body.put("newPassword", newPassword); body.put("newPassword", newPassword);
body.put("confirmPassword", confirmPassword); body.put("confirmPassword", confirmPassword);
body.put("formUrls", buildUrlsMap()); body.put("formUrls", buildUrlsMap());
@ -177,5 +177,7 @@ public abstract class UserAccountsPasswordBasePage extends UserAccountsPage {
protected abstract String passwordChangeNotPendingMessage(); protected abstract String passwordChangeNotPendingMessage();
protected abstract String passwordChangeInavlidKeyMessage();
protected abstract String templateName(); protected abstract String templateName();
} }

View file

@ -56,6 +56,11 @@ public class UserAccountsResetPasswordPage extends UserAccountsPasswordBasePage
return i18n.text("password_change_not_pending", userEmail); return i18n.text("password_change_not_pending", userEmail);
} }
@Override
protected String passwordChangeInavlidKeyMessage() {
return i18n.text("password_change_invalid_key", userEmail);
}
@Override @Override
protected String templateName() { protected String templateName() {
return TEMPLATE_NAME; return TEMPLATE_NAME;

View file

@ -134,6 +134,7 @@ public class BasicAuthenticator extends Authenticator {
userAccount.setMd5Password(""); userAccount.setMd5Password("");
userAccount.setPasswordChangeRequired(false); userAccount.setPasswordChangeRequired(false);
userAccount.setPasswordLinkExpires(0L); userAccount.setPasswordLinkExpires(0L);
userAccount.setEmailKey("");
getUserAccountsDao().updateUserAccount(userAccount); getUserAccountsDao().updateUserAccount(userAccount);
} }

View file

@ -108,7 +108,9 @@ public class LoginRedirector {
throws IOException { throws IOException {
try { try {
DisplayMessage.setMessage(request, assembleWelcomeMessage()); DisplayMessage.setMessage(request, assembleWelcomeMessage());
response.sendRedirect(getRedirectionUriForLoggedInUser()); String redirectUrl = getRedirectionUriForLoggedInUser();
log.debug("Sending redirect to path: " + redirectUrl);
response.sendRedirect(redirectUrl);
} catch (IOException e) { } catch (IOException e) {
log.debug("Problem with re-direction", e); log.debug("Problem with re-direction", e);
response.sendRedirect(getApplicationHomePageUrl()); 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() { private String getApplicationHomePageUrl() {
String contextRedirect = (String) session.getServletContext() String contextPath = request.getContextPath();
.getAttribute("postLoginRequest"); if (contextPath.equals("")) {
if (contextRedirect != null) { return "/";
if (contextRedirect.indexOf(":") == -1) {
return request.getContextPath() + contextRedirect;
} else {
return contextRedirect;
} }
else {
return contextPath;
} }
return request.getContextPath();
} }
} }

View file

@ -11,28 +11,29 @@ import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Set;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet; import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.LocaleUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.apache.jena.query.QuerySolution; import org.apache.jena.query.QuerySolution;
import org.apache.jena.query.ResultSet; import org.apache.jena.query.ResultSet;
import org.apache.jena.rdf.model.Literal; import org.apache.jena.rdf.model.Literal;
import edu.cornell.mannlib.vitro.webapp.beans.Individual; import edu.cornell.mannlib.vitro.webapp.beans.Individual;
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest; 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.ExceptionResponseValues;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.ResponseValues; 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.controller.freemarker.responsevalues.TemplateResponseValues;
import edu.cornell.mannlib.vitro.webapp.dao.jena.QueryUtils; import edu.cornell.mannlib.vitro.webapp.dao.jena.QueryUtils;
import edu.cornell.mannlib.vitro.webapp.i18n.selection.SelectedLocale; 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*/ /*Servlet to view all labels in various languages for individual*/
@ -47,12 +48,13 @@ public class ViewLabelsServlet extends FreemarkerHttpServlet{
String subjectUri = vreq.getParameter("subjectUri"); String subjectUri = vreq.getParameter("subjectUri");
body.put("subjectUri", subjectUri); body.put("subjectUri", subjectUri);
try { try {
//Get all language codes/labels in the system, and this list is sorted by language name
List<HashMap<String, String>> locales = this.getLocales(vreq);
//Get code to label hashmap - we use this to get the language name for the language code returned in the rdf literal
HashMap<String, String> localeCodeToNameMap = this.getFullCodeToLanguageNameMap(locales);
//the labels already added by the user //the labels already added by the user
ArrayList<Literal> existingLabels = this.getExistingLabels(subjectUri, vreq); ArrayList<Literal> existingLabels = this.getExistingLabels(subjectUri, vreq);
//Get all language codes/labels used in the list of existing labels
List<HashMap<String, String>> 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<String, String> localeCodeToNameMap = this.getFullCodeToLanguageNameMap(locales);
//existing labels keyed by language name and each of the list of labels is sorted by language name //existing labels keyed by language name and each of the list of labels is sorted by language name
HashMap<String, List<LabelInformation>> existingLabelsByLanguageName = this.getLabelsSortedByLanguageName(existingLabels, localeCodeToNameMap, vreq, subjectUri); HashMap<String, List<LabelInformation>> existingLabelsByLanguageName = this.getLabelsSortedByLanguageName(existingLabels, localeCodeToNameMap, vreq, subjectUri);
//Get available locales for the drop down for adding a new label, also sorted by language name //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); doGet(request, response);
} }
//get locales //get locales present in list of literals
public List<HashMap<String, String>> getLocales(VitroRequest vreq) { public List<HashMap<String, String>> getLocales(VitroRequest vreq,
List<Locale> selectables = SelectedLocale.getSelectableLocales(vreq); List<Literal> existingLiterals) {
if (selectables.isEmpty()) { Set<Locale> locales = new HashSet<Locale>();
for(Literal literal : existingLiterals) {
String language = literal.getLanguage();
if(!StringUtils.isEmpty(language)) {
locales.add(LanguageFilteringUtils.languageToLocale(language));
}
}
if (locales.isEmpty()) {
return Collections.emptyList(); return Collections.emptyList();
} }
List<HashMap<String, String>> list = new ArrayList<HashMap<String, String>>(); List<HashMap<String, String>> list = new ArrayList<HashMap<String, String>>();
Locale currentLocale = SelectedLocale.getCurrentLocale(vreq); Locale currentLocale = SelectedLocale.getCurrentLocale(vreq);
for (Locale locale : selectables) { for (Locale locale : locales) {
try { try {
list.add(buildLocaleMap(locale, currentLocale)); list.add(buildLocaleMap(locale, currentLocale));
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
log.warn("Can't show the Locale selector for '" + locale log.warn("Can't show locale '" + locale + "': " + e);
+ "': " + e);
} }
} }
@ -188,8 +196,8 @@ public class ViewLabelsServlet extends FreemarkerHttpServlet{
ArrayList<Literal> labels = new ArrayList<Literal>(); ArrayList<Literal> labels = new ArrayList<Literal>();
try { try {
//We want to get the labels for all the languages, not just the display language // Show only labels with current language filtering
ResultSet results = QueryUtils.getLanguageNeutralQueryResults(queryStr, vreq); ResultSet results = QueryUtils.getQueryResults(queryStr, vreq);
while (results.hasNext()) { while (results.hasNext()) {
QuerySolution soln = results.nextSolution(); QuerySolution soln = results.nextSolution();
Literal nodeLiteral = soln.get("label").asLiteral(); Literal nodeLiteral = soln.get("label").asLiteral();

View file

@ -6,14 +6,13 @@ import java.io.IOException;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; 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.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.apache.jena.query.QuerySolution; import org.apache.jena.query.QuerySolution;
import org.apache.jena.query.ResultSet; 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.utils.dataGetter.ExecuteDataRetrieval;
import edu.cornell.mannlib.vitro.webapp.web.beanswrappers.ReadOnlyBeansWrapper; 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.IndividualTemplateModel;
import edu.cornell.mannlib.vitro.webapp.web.templatemodels.individual.IndividualTemplateModelBuilder;
import edu.ucsf.vitro.opensocial.OpenSocialManager; import edu.ucsf.vitro.opensocial.OpenSocialManager;
import freemarker.ext.beans.BeansWrapper; import freemarker.ext.beans.BeansWrapper;
import freemarker.template.TemplateModel; import freemarker.template.TemplateModel;
@ -123,8 +123,10 @@ class IndividualResponseBuilder {
* into the data model: no real data can be modified. * into the data model: no real data can be modified.
*/ */
// body.put("individual", wrap(itm, BeansWrapper.EXPOSE_SAFE)); // body.put("individual", wrap(itm, BeansWrapper.EXPOSE_SAFE));
body.put("labelCount", getLabelCount(itm.getUri(), vreq)); LabelAndLanguageCount labelAndLanguageCount = getLabelAndLanguageCount(
body.put("languageCount", getLanguagesRepresentedCount(itm.getUri(), vreq)); itm.getUri(), vreq);
body.put("labelCount", labelAndLanguageCount.getLabelCount());
body.put("languageCount", labelAndLanguageCount.getLanguageCount());
//We also need to know the number of available locales //We also need to know the number of available locales
body.put("localesCount", SelectedLocale.getSelectableLocales(vreq).size()); body.put("localesCount", SelectedLocale.getSelectableLocales(vreq).size());
body.put("profileType", getProfileType(itm.getUri(), vreq)); body.put("profileType", getProfileType(itm.getUri(), vreq));
@ -282,61 +284,103 @@ class IndividualResponseBuilder {
return map; return map;
} }
private static String LABEL_COUNT_QUERY = "" private static String LABEL_QUERY = ""
+ "PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> \n" + "PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> \n"
+ "SELECT ( str(COUNT(?label)) AS ?labelCount ) WHERE { \n" + "SELECT ?label WHERE { \n"
+ " ?subject rdfs:label ?label \n" + " ?subject rdfs:label ?label \n"
+ " FILTER isLiteral(?label) \n" + " FILTER isLiteral(?label) \n"
+ "}" ; + "}" ;
private static String DISTINCT_LANGUAGE_QUERY = "" // Queries that were previously used for counts via RDFService that didn't
+ "PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> \n" // filter results by language. With language filtering, aggregate
+ "SELECT ( str(COUNT(DISTINCT lang(?label))) AS ?languageCount ) WHERE { \n" // functions like COUNT() cannot be used.
+ " ?subject rdfs:label ?label \n"
+ " FILTER isLiteral(?label) \n"
+ "}" ;
private static Integer getLabelCount(String subjectUri, VitroRequest vreq) { // private static String LABEL_COUNT_QUERY = ""
String queryStr = QueryUtils.subUriForQueryVar(LABEL_COUNT_QUERY, "subject", subjectUri); // + "PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> \n"
// + "SELECT ( str(COUNT(?label)) AS ?labelCount ) WHERE { \n"
// + " ?subject rdfs:label ?label \n"
// + " FILTER isLiteral(?label) \n"
// + "}" ;
// private static String DISTINCT_LANGUAGE_QUERY = ""
// + "PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> \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<String> distinctLanguages = new HashSet<String>();
String queryStr = QueryUtils.subUriForQueryVar(LABEL_QUERY, "subject", subjectUri);
log.debug("queryStr = " + queryStr); log.debug("queryStr = " + queryStr);
int theCount = 0; int labelCount = 0;
try { try {
//ResultSet results = QueryUtils.getQueryResults(queryStr, vreq); ResultSet results = QueryUtils.getQueryResults(queryStr, vreq);
//Get query results across all languages in order for template to show manage labels link correctly while(results.hasNext()) {
ResultSet results = QueryUtils.getLanguageNeutralQueryResults(queryStr, vreq); QuerySolution qsoln = results.next();
if (results.hasNext()) { labelCount++;
QuerySolution soln = results.nextSolution(); String lang = qsoln.getLiteral("label").getLanguage();
RDFNode labelCount = soln.get("labelCount"); if(lang == null) {
if (labelCount != null && labelCount.isLiteral()) { lang = "";
theCount = labelCount.asLiteral().getInt();
} }
distinctLanguages.add(lang);
} }
} catch (Exception e) { } catch (Exception e) {
log.error(e, 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 //what is the number of languages represented across the labels
private static Integer getLanguagesRepresentedCount(String subjectUri, VitroRequest vreq) { // This version not compatible with language-filtering RDF services
String queryStr = QueryUtils.subUriForQueryVar(DISTINCT_LANGUAGE_QUERY, "subject", subjectUri); // private static Integer getLanguagesRepresentedCount(String subjectUri, VitroRequest vreq) {
log.debug("queryStr = " + queryStr); // String queryStr = QueryUtils.subUriForQueryVar(DISTINCT_LANGUAGE_QUERY, "subject", subjectUri);
int theCount = 0; // log.debug("queryStr = " + queryStr);
try { // int theCount = 0;
// try {
ResultSet results = QueryUtils.getLanguageNeutralQueryResults(queryStr, vreq); //
if (results.hasNext()) { // ResultSet results = QueryUtils.getLanguageNeutralQueryResults(queryStr, vreq);
QuerySolution soln = results.nextSolution(); // if (results.hasNext()) {
RDFNode languageCount = soln.get("languageCount"); // QuerySolution soln = results.nextSolution();
if (languageCount != null && languageCount.isLiteral()) { // RDFNode languageCount = soln.get("languageCount");
theCount = languageCount.asLiteral().getInt(); // if (languageCount != null && languageCount.isLiteral()) {
} // theCount = languageCount.asLiteral().getInt();
} // log.info("Language count is " + theCount);
} catch (Exception e) { // }
log.error(e, e); // }
} // } catch (Exception e) {
return theCount; // log.error(e, e);
} // }
// log.info("Returning language count " + theCount);
// return theCount;
// }
private static String PROFILE_TYPE_QUERY = "" private static String PROFILE_TYPE_QUERY = ""
+ "PREFIX display: <http://vitro.mannlib.cornell.edu/ontologies/display/1.1#> \n" + "PREFIX display: <http://vitro.mannlib.cornell.edu/ontologies/display/1.1#> \n"

View file

@ -155,6 +155,7 @@ public class VitroVocabulary {
public static final String USERACCOUNT_LAST_LOGIN_TIME = VITRO_AUTH + "lastLoginTime"; 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_STATUS = VITRO_AUTH + "status";
public static final String USERACCOUNT_PASSWORD_LINK_EXPIRES = VITRO_AUTH + "passwordLinkExpires"; 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_PASSWORD_CHANGE_REQUIRED = VITRO_AUTH + "passwordChangeRequired";
public static final String USERACCOUNT_EXTERNAL_AUTH_ID = VITRO_AUTH + "externalAuthId"; public static final String USERACCOUNT_EXTERNAL_AUTH_ID = VITRO_AUTH + "externalAuthId";
public static final String USERACCOUNT_EXTERNAL_AUTH_ONLY = VITRO_AUTH + "externalAuthOnly"; public static final String USERACCOUNT_EXTERNAL_AUTH_ONLY = VITRO_AUTH + "externalAuthOnly";

View file

@ -121,6 +121,7 @@ public class JenaBaseDaoCon {
protected DatatypeProperty USERACCOUNT_LAST_LOGIN_TIME = _constModel.createDatatypeProperty(VitroVocabulary.USERACCOUNT_LAST_LOGIN_TIME); 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_STATUS = _constModel.createDatatypeProperty(VitroVocabulary.USERACCOUNT_STATUS);
protected DatatypeProperty USERACCOUNT_PASSWORD_LINK_EXPIRES = _constModel.createDatatypeProperty(VitroVocabulary.USERACCOUNT_PASSWORD_LINK_EXPIRES); 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_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_ID = _constModel.createDatatypeProperty(VitroVocabulary.USERACCOUNT_EXTERNAL_AUTH_ID);
protected DatatypeProperty USERACCOUNT_EXTERNAL_AUTH_ONLY = _constModel.createDatatypeProperty(VitroVocabulary.USERACCOUNT_EXTERNAL_AUTH_ONLY); protected DatatypeProperty USERACCOUNT_EXTERNAL_AUTH_ONLY = _constModel.createDatatypeProperty(VitroVocabulary.USERACCOUNT_EXTERNAL_AUTH_ONLY);

View file

@ -12,10 +12,10 @@ import java.util.function.Supplier;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.apache.jena.graph.Capabilities; import org.apache.jena.graph.Capabilities;
import org.apache.jena.graph.Graph; import org.apache.jena.graph.Graph;
import org.apache.jena.graph.GraphEventManager; import org.apache.jena.graph.GraphEventManager;
import org.apache.jena.graph.GraphListener;
import org.apache.jena.graph.GraphStatisticsHandler; import org.apache.jena.graph.GraphStatisticsHandler;
import org.apache.jena.graph.Node; import org.apache.jena.graph.Node;
import org.apache.jena.graph.TransactionHandler; 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.GraphWithPerform;
import org.apache.jena.graph.impl.SimpleEventManager; import org.apache.jena.graph.impl.SimpleEventManager;
import org.apache.jena.query.QuerySolution; 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.Model;
import org.apache.jena.rdf.model.ModelFactory; import org.apache.jena.rdf.model.ModelFactory;
import org.apache.jena.rdf.model.StmtIterator; import org.apache.jena.rdf.model.StmtIterator;
@ -409,7 +408,18 @@ public class RDFServiceGraph implements GraphWithPerform {
@Override @Override
public GraphEventManager getEventManager() { public GraphEventManager getEventManager() {
if (eventManager == null) { 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; return eventManager;
} }
@ -595,21 +605,7 @@ public class RDFServiceGraph implements GraphWithPerform {
} }
public static Model createRDFServiceModel(final RDFServiceGraph g) { public static Model createRDFServiceModel(final RDFServiceGraph g) {
Model m = VitroModelFactory.createModelForGraph(g); return 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;
} }
@Override @Override

View file

@ -4,7 +4,6 @@ package edu.cornell.mannlib.vitro.webapp.dao.jena;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Random; import java.util.Random;
@ -98,6 +97,8 @@ public class UserAccountsDaoJena extends JenaBaseDao implements UserAccountsDao
u.setOldPassword(getPropertyStringValue(r, USERACCOUNT_OLD_PASSWORD)); u.setOldPassword(getPropertyStringValue(r, USERACCOUNT_OLD_PASSWORD));
u.setPasswordLinkExpires(getPropertyLongValue(r, u.setPasswordLinkExpires(getPropertyLongValue(r,
USERACCOUNT_PASSWORD_LINK_EXPIRES)); USERACCOUNT_PASSWORD_LINK_EXPIRES));
u.setEmailKey(getPropertyStringValue(r,USERACCOUNT_EMAIL_KEY));
u.setPasswordChangeRequired(getPropertyBooleanValue(r, u.setPasswordChangeRequired(getPropertyBooleanValue(r,
USERACCOUNT_PASSWORD_CHANGE_REQUIRED)); USERACCOUNT_PASSWORD_CHANGE_REQUIRED));
u.setExternalAuthOnly(getPropertyBooleanValue(r, u.setExternalAuthOnly(getPropertyBooleanValue(r,
@ -240,6 +241,8 @@ public class UserAccountsDaoJena extends JenaBaseDao implements UserAccountsDao
userAccount.getLoginCount(), model); userAccount.getLoginCount(), model);
addPropertyLongValue(res, USERACCOUNT_LAST_LOGIN_TIME, addPropertyLongValue(res, USERACCOUNT_LAST_LOGIN_TIME,
userAccount.getLastLoginTime(), model); userAccount.getLastLoginTime(), model);
addPropertyStringValue(res, USERACCOUNT_EMAIL_KEY,
userAccount.getEmailKey(), model);
if (userAccount.getStatus() != null) { if (userAccount.getStatus() != null) {
addPropertyStringValue(res, USERACCOUNT_STATUS, userAccount addPropertyStringValue(res, USERACCOUNT_STATUS, userAccount
.getStatus().toString(), model); .getStatus().toString(), model);
@ -306,6 +309,8 @@ public class UserAccountsDaoJena extends JenaBaseDao implements UserAccountsDao
userAccount.getLoginCount(), model); userAccount.getLoginCount(), model);
updatePropertyLongValue(res, USERACCOUNT_LAST_LOGIN_TIME, updatePropertyLongValue(res, USERACCOUNT_LAST_LOGIN_TIME,
userAccount.getLastLoginTime(), model); userAccount.getLastLoginTime(), model);
updatePropertyStringValue(res, USERACCOUNT_EMAIL_KEY,
userAccount.getEmailKey(), model);
if (userAccount.getStatus() == null) { if (userAccount.getStatus() == null) {
updatePropertyStringValue(res, USERACCOUNT_STATUS, null, model); updatePropertyStringValue(res, USERACCOUNT_STATUS, null, model);
} else { } else {

View file

@ -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.IdModelSelector;
import edu.cornell.mannlib.vitro.webapp.edit.n3editing.configuration.StandardModelSelector; 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;
import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess.LanguageOption;
public abstract class BaseEditConfigurationGenerator implements EditConfigurationGenerator { public abstract class BaseEditConfigurationGenerator implements EditConfigurationGenerator {
@ -63,6 +64,7 @@ public abstract class BaseEditConfigurationGenerator implements EditConfiguratio
setupModelSelectorsFromVitroRequest(vreq, editConfig); setupModelSelectorsFromVitroRequest(vreq, editConfig);
OntModel queryModel = ModelAccess.on(vreq).getOntModel(); OntModel queryModel = ModelAccess.on(vreq).getOntModel();
OntModel languageNeutralModel = vreq.getLanguageNeutralUnionFullModel();
if( editConfig.getSubjectUri() == null) if( editConfig.getSubjectUri() == null)
editConfig.setSubjectUri( EditConfigurationUtils.getSubjectUri(vreq)); editConfig.setSubjectUri( EditConfigurationUtils.getSubjectUri(vreq));
@ -78,7 +80,10 @@ public abstract class BaseEditConfigurationGenerator implements EditConfiguratio
editConfig.prepareForObjPropUpdate(queryModel); editConfig.prepareForObjPropUpdate(queryModel);
} else if( dataKey != null ) { // edit of a data prop statement } else if( dataKey != null ) { // edit of a data prop statement
//do nothing since the data prop form generator must take care of it //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{ } else{
//this might be a create new or a form //this might be a create new or a form
editConfig.prepareForNonUpdate(queryModel); editConfig.prepareForNonUpdate(queryModel);

View file

@ -15,6 +15,7 @@ import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Set;
import javax.servlet.http.HttpSession; 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.FoafNameToRdfsLabelPreprocessor;
import edu.cornell.mannlib.vitro.webapp.edit.n3editing.configuration.preprocessors.ManageLabelsForIndividualPreprocessor; 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.i18n.selection.SelectedLocale;
import edu.cornell.mannlib.vitro.webapp.rdfservice.filter.LanguageFilteringUtils;
import edu.cornell.mannlib.vitro.webapp.web.templatemodels.individual.DataPropertyStatementTemplateModel; import edu.cornell.mannlib.vitro.webapp.web.templatemodels.individual.DataPropertyStatementTemplateModel;
/** /**
@ -202,12 +204,12 @@ public class ManageLabelsForIndividualGenerator extends BaseEditConfigurationGen
private void addFormSpecificData(EditConfigurationVTwo config, private void addFormSpecificData(EditConfigurationVTwo config,
VitroRequest vreq) { VitroRequest vreq) {
//Get all language codes/labels in the system, and this list is sorted by language name
List<HashMap<String, String>> locales = this.getLocales(vreq);
//Get code to label hashmap - we use this to get the language name for the language code returned in the rdf literal
HashMap<String, String> localeCodeToNameMap = this.getFullCodeToLanguageNameMap(locales);
//the labels already added by the user //the labels already added by the user
ArrayList<Literal> existingLabels = this.getExistingLabels(config.getSubjectUri(), vreq); ArrayList<Literal> existingLabels = this.getExistingLabels(config.getSubjectUri(), vreq);
//Get language codes/labels for languages present in the existing labels
List<HashMap<String, String>> 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<String, String> localeCodeToNameMap = this.getFullCodeToLanguageNameMap(locales);
int numberExistingLabels = existingLabels.size(); int numberExistingLabels = existingLabels.size();
//existing labels keyed by language name and each of the list of labels is sorted by language name //existing labels keyed by language name and each of the list of labels is sorted by language name
HashMap<String, List<LabelInformation>> existingLabelsByLanguageName = this.getLabelsSortedByLanguageName(existingLabels, localeCodeToNameMap, config, vreq); HashMap<String, List<LabelInformation>> existingLabelsByLanguageName = this.getLabelsSortedByLanguageName(existingLabels, localeCodeToNameMap, config, vreq);
@ -373,8 +375,9 @@ public class ManageLabelsForIndividualGenerator extends BaseEditConfigurationGen
ArrayList<Literal> labels = new ArrayList<Literal>(); ArrayList<Literal> labels = new ArrayList<Literal>();
try { try {
//We want to get the labels for all the languages, not just the display language // Get results filtered to current locale so as to be consistent
ResultSet results = QueryUtils.getLanguageNeutralQueryResults(queryStr, vreq); // with other editing forms.
ResultSet results = QueryUtils.getQueryResults(queryStr, vreq);
while (results.hasNext()) { while (results.hasNext()) {
QuerySolution soln = results.nextSolution(); QuerySolution soln = results.nextSolution();
Literal nodeLiteral = soln.get("label").asLiteral(); Literal nodeLiteral = soln.get("label").asLiteral();
@ -401,30 +404,32 @@ public class ManageLabelsForIndividualGenerator extends BaseEditConfigurationGen
return template; return template;
} }
//get locales present in list of literals
public List<HashMap<String, String>> getLocales(VitroRequest vreq,
//get locales List<Literal> existingLiterals) {
public List<HashMap<String, String>> getLocales(VitroRequest vreq) { Set<Locale> locales = new HashSet<Locale>();
List<Locale> selectables = SelectedLocale.getSelectableLocales(vreq); for(Literal literal : existingLiterals) {
if (selectables.isEmpty()) { String language = literal.getLanguage();
if(!StringUtils.isEmpty(language)) {
locales.add(LanguageFilteringUtils.languageToLocale(language));
}
}
if (locales.isEmpty()) {
return Collections.emptyList(); return Collections.emptyList();
} }
List<HashMap<String, String>> list = new ArrayList<HashMap<String, String>>(); List<HashMap<String, String>> list = new ArrayList<HashMap<String, String>>();
Locale currentLocale = SelectedLocale.getCurrentLocale(vreq); Locale currentLocale = SelectedLocale.getCurrentLocale(vreq);
for (Locale locale : selectables) { for (Locale locale : locales) {
try { try {
list.add(buildLocaleMap(locale, currentLocale)); list.add(buildLocaleMap(locale, currentLocale));
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
log.warn("Can't show the Locale selector for '" + locale log.warn("Can't show locale '" + locale + "': " + e);
+ "': " + e);
} }
} }
return list; return list;
} }
public HashMap<String, String> getFullCodeToLanguageNameMap(List<HashMap<String, String>> localesList) { public HashMap<String, String> getFullCodeToLanguageNameMap(List<HashMap<String, String>> localesList) {
HashMap<String, String> codeToLanguageMap = new HashMap<String, String>(); HashMap<String, String> codeToLanguageMap = new HashMap<String, String>();
for(Map<String, String> locale: localesList) { for(Map<String, String> locale: localesList) {

View file

@ -68,12 +68,15 @@ public class EditRequestDispatchController extends FreemarkerHttpServlet {
//TODO: Create this generator //TODO: Create this generator
final String RDFS_LABEL_FORM = "edu.cornell.mannlib.vitro.webapp.edit.n3editing.configuration.generators.RDFSLabelGenerator"; 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 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 @Override
protected AuthorizationRequest requiredActions(VitroRequest vreq) { protected AuthorizationRequest requiredActions(VitroRequest vreq) {
// If request is for new individual, return simple do back end editing action permission // If request is for new individual, return simple do back end editing action permission
if (StringUtils.isNotEmpty(EditConfigurationUtils.getTypeOfNew(vreq))) { if (StringUtils.isNotEmpty(EditConfigurationUtils.getTypeOfNew(vreq))) {
return SimplePermission.DO_BACK_END_EDITING.ACTION; 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 // Check if this statement can be edited here and return unauthorized if not
String subjectUri = EditConfigurationUtils.getSubjectUri(vreq); String subjectUri = EditConfigurationUtils.getSubjectUri(vreq);

View file

@ -202,7 +202,7 @@ public class RequestModelAccessImpl implements RequestModelAccess {
@Override @Override
public OntModel getOntModel(String name, LanguageOption... options) { 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) { private OntModel getOntModel(OntModelKey key) {

View file

@ -9,6 +9,7 @@ import org.apache.jena.query.QuerySolution;
import org.apache.jena.query.ResultSet; import org.apache.jena.query.ResultSet;
import org.apache.jena.rdf.model.Model; import org.apache.jena.rdf.model.Model;
import org.apache.jena.sparql.engine.binding.Binding; import org.apache.jena.sparql.engine.binding.Binding;
import org.apache.jena.sparql.engine.binding.BindingUtils;
public class FilteredResultSet implements ResultSet { public class FilteredResultSet implements ResultSet {
@ -53,7 +54,7 @@ public class FilteredResultSet implements ResultSet {
@Override @Override
public Binding nextBinding() { public Binding nextBinding() {
throw new UnsupportedOperationException("Can we ignore this?"); return BindingUtils.asBinding(nextSolution());
} }
@Override @Override

View file

@ -36,8 +36,14 @@ public class LangSort {
} }
protected int compareLangs(String t1lang, String t2lang) { protected int compareLangs(String t1lang, String t2lang) {
int index1 = languageIndex(t1lang);
int index2 = languageIndex(t2lang);
if(index1 == index2) {
return t1lang.compareTo(t2lang);
} else {
return languageIndex(t1lang) - languageIndex(t2lang); return languageIndex(t1lang) - languageIndex(t2lang);
} }
}
/** /**
* Return index of exact match, or index of partial match, or * Return index of exact match, or index of partial match, or

View file

@ -201,8 +201,8 @@ public abstract class RDFServiceJena extends RDFServiceImpl implements RDFServic
log.debug("blank node model size " + blankNodeModel.size()); log.debug("blank node model size " + blankNodeModel.size());
if (blankNodeModel.size() == 1) { if (blankNodeModel.size() == 1) {
log.warn("Deleting single triple with blank node: " + blankNodeModel); log.debug("Deleting single triple with blank node: " + blankNodeModel);
log.warn("This likely indicates a problem; excessive data may be deleted."); 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); Query rootFinderQuery = QueryFactory.create(BNODE_ROOT_QUERY);

View file

@ -13,6 +13,8 @@ import java.nio.file.Paths;
import java.util.List; import java.util.List;
import org.apache.jena.rdf.model.Model; import org.apache.jena.rdf.model.Model;
import org.apache.jena.rdf.model.RDFNode;
import edu.cornell.mannlib.vitro.webapp.rdfservice.ResultSetConsumer; import edu.cornell.mannlib.vitro.webapp.rdfservice.ResultSetConsumer;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
@ -75,9 +77,17 @@ public class RDFServiceTDB extends RDFServiceJena {
notifyListenersOfPreChangeEvents(changeSet); notifyListenersOfPreChangeEvents(changeSet);
dataset.begin(ReadWrite.WRITE); dataset.begin(ReadWrite.WRITE);
try {
boolean committed = false;
try { try {
applyChangeSetToModel(changeSet, dataset); applyChangeSetToModel(changeSet, dataset);
dataset.commit(); dataset.commit();
committed = true;
} finally {
if(!committed) {
dataset.abort();
}
}
} finally { } finally {
dataset.end(); dataset.end();
} }
@ -93,6 +103,10 @@ public class RDFServiceTDB extends RDFServiceJena {
} }
} }
@Override
public boolean preferPreciseOptionals() {
return true;
}
@Override @Override
public void close() { public void close() {
@ -232,6 +246,28 @@ public class RDFServiceTDB extends RDFServiceJena {
return isEquivalentGraph(graphURI, inStream, ModelSerializationFormat.NTRIPLE); 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. * Convert all of the references to integer compatible type to "integer" in the serialized graph.
* *

View file

@ -202,23 +202,18 @@ public class DeveloperSettings {
File dsFile = homeDir.resolve("config/developer.properties") File dsFile = homeDir.resolve("config/developer.properties")
.toFile(); .toFile();
if (!dsFile.exists()) {
dsFile = homeDir.resolve("config/default.developer.properties").toFile();
}
try (FileReader reader = new FileReader(dsFile)) { try (FileReader reader = new FileReader(dsFile)) {
Properties dsProps = new Properties(); Properties dsProps = new Properties();
dsProps.load(reader); dsProps.load(reader);
devSettings.updateFromProperties(dsProps); devSettings.updateFromProperties(dsProps);
log.info(devSettings); log.info(devSettings);
ss.info(this, "Loaded the '" + dsFile.getName() + "' file: " ss.info(this, "Loaded the 'developer.properties' file: "
+ devSettings); + devSettings);
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
ss.info(this, "Neither 'developer.properties' nor 'default.developer.properties' " + ss.info(this, "'developer.properties' file does not exist.");
"files exist.");
} catch (Exception e) { } catch (Exception e) {
ss.warning(this, ss.warning(this,
"Failed to load the '" + dsFile.getAbsolutePath() + "' file.", e); "Failed to load the 'developer.properties' file.", e);
} }
} }

View file

@ -1,15 +1,24 @@
package org.vivoweb.linkeddatafragments.servlet; package org.vivoweb.linkeddatafragments.servlet;
import com.fasterxml.jackson.databind.JsonNode; import java.io.IOException;
import edu.cornell.mannlib.vitro.webapp.beans.Ontology; import java.io.InputStream;
import edu.cornell.mannlib.vitro.webapp.controller.VitroHttpServlet; import java.io.StringReader;
import edu.cornell.mannlib.vitro.webapp.dao.OntologyDao; import java.util.HashMap;
import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess; import java.util.List;
import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService; import java.util.Map.Entry;
import edu.cornell.mannlib.vitro.webapp.config.ConfigurationProperties;
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.commons.io.IOUtils;
import org.apache.jena.riot.Lang;
import org.apache.commons.lang3.StringUtils; 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.config.ConfigReader;
import org.linkeddatafragments.datasource.DataSourceFactory; import org.linkeddatafragments.datasource.DataSourceFactory;
import org.linkeddatafragments.datasource.DataSourceTypesRegistry; import org.linkeddatafragments.datasource.DataSourceTypesRegistry;
@ -22,26 +31,19 @@ import org.linkeddatafragments.fragments.ILinkedDataFragment;
import org.linkeddatafragments.fragments.ILinkedDataFragmentRequest; import org.linkeddatafragments.fragments.ILinkedDataFragmentRequest;
import org.linkeddatafragments.util.MIMEParse; import org.linkeddatafragments.util.MIMEParse;
import org.linkeddatafragments.views.ILinkedDataFragmentWriter; 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.RDFServiceBasedRequestProcessorForTPFs;
import org.vivoweb.linkeddatafragments.datasource.rdfservice.RDFServiceDataSourceType; import org.vivoweb.linkeddatafragments.datasource.rdfservice.RDFServiceDataSourceType;
import org.vivoweb.linkeddatafragments.views.HtmlTriplePatternFragmentWriterImpl;
import org.vivoweb.linkeddatafragments.views.LinkedDataFragmentWriterFactory;
import javax.servlet.ServletConfig; import com.fasterxml.jackson.databind.JsonNode;
import javax.servlet.ServletContext;
import javax.servlet.ServletException; import edu.cornell.mannlib.vitro.webapp.beans.Ontology;
import javax.servlet.annotation.WebServlet; import edu.cornell.mannlib.vitro.webapp.config.ConfigurationProperties;
import javax.servlet.http.HttpServletRequest; import edu.cornell.mannlib.vitro.webapp.controller.VitroHttpServlet;
import javax.servlet.http.HttpServletResponse; import edu.cornell.mannlib.vitro.webapp.dao.OntologyDao;
import java.io.File; import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess;
import java.io.IOException; import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService;
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;
/** /**
* Servlet that responds with a Linked Data Fragment. * Servlet that responds with a Linked Data Fragment.
@ -52,29 +54,13 @@ public class VitroLinkedDataFragmentServlet extends VitroHttpServlet {
private final static long serialVersionUID = 1L; private final static long serialVersionUID = 1L;
private static final String PROPERTY_TPF_ACTIVE_FLAG = "tpf.activeFlag"; private static final String PROPERTY_TPF_ACTIVE_FLAG = "tpf.activeFlag";
private static final Log log = LogFactory.getLog(VitroLinkedDataFragmentServlet.class);
private ConfigReader config; private ConfigReader config;
private final HashMap<String, IDataSource> dataSources = new HashMap<>(); private final HashMap<String, IDataSource> dataSources = new HashMap<>();
private final Collection<String> mimeTypes = new ArrayList<>();
private ConfigurationProperties configProps; private ConfigurationProperties configProps;
private String tpfActiveFlag; 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 @Override
public void init(ServletConfig servletConfig) throws ServletException { public void init(ServletConfig servletConfig) throws ServletException {
try { try {
@ -82,11 +68,11 @@ public class VitroLinkedDataFragmentServlet extends VitroHttpServlet {
configProps = ConfigurationProperties.getBean(ctx); configProps = ConfigurationProperties.getBean(ctx);
if (!configurationPresent()) { if (!configurationPresent()) {
throw new ServletException("TPF is currently disabled. To enable, add 'tpfActive.flag=true' to the runtime.properties."); throw new ServletException("TPF is currently disabled. To enable, add '"
} else { + PROPERTY_TPF_ACTIVE_FLAG + " = true' to runtime.properties.");
if (!tpfActiveFlag.equalsIgnoreCase("true")) { } 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, set '"
} + PROPERTY_TPF_ACTIVE_FLAG + " = true' in runtime.properties.");
} }
RDFService rdfService = ModelAccess.on(ctx).getRDFService(); RDFService rdfService = ModelAccess.on(ctx).getRDFService();
@ -215,18 +201,18 @@ public class VitroLinkedDataFragmentServlet extends VitroHttpServlet {
writer.writeFragment(response.getOutputStream(), dataSource, fragment, ldfRequest); writer.writeFragment(response.getOutputStream(), dataSource, fragment, ldfRequest);
} catch (DataSourceNotFoundException ex) { } catch (DataSourceNotFoundException ex) {
log.error(ex, ex);
try { try {
response.setStatus(404); response.setStatus(404);
writer.writeNotFound(response.getOutputStream(), request); writer.writeNotFound(response.getOutputStream(), request);
} catch (Exception ex1) { } catch (Exception ex1) {
log.error(ex1, ex1);
throw new ServletException(ex1); throw new ServletException(ex1);
} }
} catch (Exception e) {
response.setStatus(500);
writer.writeError(response.getOutputStream(), e);
} }
} catch (Exception e) { } catch (Exception e) {
log.error(e, e);
throw new ServletException(e); throw new ServletException(e);
} }
finally { finally {

View file

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

View file

@ -7,13 +7,13 @@
<groupId>org.vivoweb</groupId> <groupId>org.vivoweb</groupId>
<artifactId>vitro-dependencies</artifactId> <artifactId>vitro-dependencies</artifactId>
<version>1.11.2-SNAPSHOT</version> <version>1.12.0-SNAPSHOT</version>
<packaging>pom</packaging> <packaging>pom</packaging>
<parent> <parent>
<groupId>org.vivoweb</groupId> <groupId>org.vivoweb</groupId>
<artifactId>vitro-project</artifactId> <artifactId>vitro-project</artifactId>
<version>1.11.2-SNAPSHOT</version> <version>1.12.0-SNAPSHOT</version>
<relativePath>..</relativePath> <relativePath>..</relativePath>
</parent> </parent>

View file

@ -7,13 +7,13 @@
<groupId>org.vivoweb</groupId> <groupId>org.vivoweb</groupId>
<artifactId>vitro-home</artifactId> <artifactId>vitro-home</artifactId>
<version>1.11.2-SNAPSHOT</version> <version>1.12.0-SNAPSHOT</version>
<packaging>pom</packaging> <packaging>pom</packaging>
<parent> <parent>
<groupId>org.vivoweb</groupId> <groupId>org.vivoweb</groupId>
<artifactId>vitro-project</artifactId> <artifactId>vitro-project</artifactId>
<version>1.11.2-SNAPSHOT</version> <version>1.12.0-SNAPSHOT</version>
<relativePath>..</relativePath> <relativePath>..</relativePath>
</parent> </parent>

View file

@ -26,7 +26,7 @@
:hasSearchIndexer :basicSearchIndexer ; :hasSearchIndexer :basicSearchIndexer ;
:hasImageProcessor :iioImageProcessor ; :hasImageProcessor :iioImageProcessor ;
:hasFileStorage :ptiFileStorage ; :hasFileStorage :ptiFileStorage ;
:hasContentTripleSource :sdbContentTripleSource ; :hasContentTripleSource :tdbContentTripleSource ;
:hasConfigurationTripleSource :tdbConfigurationTripleSource ; :hasConfigurationTripleSource :tdbConfigurationTripleSource ;
:hasTBoxReasonerModule :jfactTBoxReasonerModule . :hasTBoxReasonerModule :jfactTBoxReasonerModule .
@ -82,22 +82,22 @@
# ---------------------------- # ----------------------------
# #
# Content triples source module: holds data contents # 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. # 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. # endpoint, or a Virtuoso endpoint, with parameters as shown.
# #
:sdbContentTripleSource #:sdbContentTripleSource
a vitroWebapp:triplesource.impl.sdb.ContentTripleSourceSDB , # a vitroWebapp:triplesource.impl.sdb.ContentTripleSourceSDB ,
vitroWebapp:modules.tripleSource.ContentTripleSource . # vitroWebapp:modules.tripleSource.ContentTripleSource .
#:tdbContentTripleSource :tdbContentTripleSource
# a vitroWebapp:triplesource.impl.tdb.ContentTripleSourceTDB , a vitroWebapp:triplesource.impl.tdb.ContentTripleSourceTDB ,
# vitroWebapp:modules.tripleSource.ContentTripleSource ; vitroWebapp:modules.tripleSource.ContentTripleSource ;
# # May be an absolute path, or relative to the Vitro home directory. # May be an absolute path, or relative to the Vitro home directory.
# :hasTdbDirectory "tdbContentModels" . :hasTdbDirectory "tdbContentModels" .
#:sparqlContentTripleSource #:sparqlContentTripleSource
# a vitroWebapp:triplesource.impl.virtuoso.ContentTripleSourceSPARQL , # a vitroWebapp:triplesource.impl.virtuoso.ContentTripleSourceSPARQL ,

View file

@ -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: # URL of Solr context used in local Vitro search. This will usually consist of:
# scheme + server_name + port + vitro_webapp_name + "solr" # scheme + server_name + port + "solr" + solr_core_name
# In the standard installation, the Solr context will be on the same server as Vitro, # In a standard Solr installation, the Solr service will be available on port
# and in the same Tomcat instance. The path will be the Vitro webapp.name (specified # 8983. The path will be /solr followed by the name used when adding a core
# above) + "solr" # for Vitro.
# Example: # Example:
# vitro.local.solr.url = http://localhost:8080/vitrosolr # vitro.local.solr.url = http://localhost:8983/solr/vitrocore
vitro.local.solr.url = http://localhost:8080/vitrosolr #
vitro.local.solr.url = http://localhost:8983/solr/vitrocore
# #
# Email parameters which VIVO can use to send mail. If these are left empty, # 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 # the "Contact Us" form will be disabled and users will not be notified of
# changes to their accounts. # changes to their accounts.
# # Example:
email.smtpHost = smtp.my.domain.edu # email.smtpHost = smtp.mydomain.edu
email.replyTo = vivoAdmin@my.domain.edu # email.replyTo = vitroAdmin@mydomain.edu
#
email.smtpHost =
email.replyTo =
# #
# The basic parameters for a MySQL database connection. Change the end of the # NOTE: VitroConnection.DataSource.* properties are only used in conjuction with
# URL to reflect your database name (if it is not "vitro"). Change the username # an SDB triple store.
# and password to match the authorized user you created in MySQL.
# #
VitroConnection.DataSource.url = jdbc:mysql://localhost/vitro # The basic parameters for a database connection. Change the end of the
VitroConnection.DataSource.username = vitroweb # URL to reflect your database name (if it is not "vitrodb"). Change the username
VitroConnection.DataSource.password = vitrovitro # 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. # The maximum number of active connections in the database connection pool.
# Increase this value to support a greater number of concurrent page requests. # 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 # The maximum number of database connections that will be allowed
# to remain idle in the connection pool. Default is 25% # to remain idle in the connection pool. Default is 25%
# of the maximum number of active connections. # 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. # MySQL.
# #
VitroConnection.DataSource.dbtype = MySQL # VitroConnection.DataSource.dbtype = MySQL
VitroConnection.DataSource.driver = com.mysql.jdbc.Driver # VitroConnection.DataSource.driver = com.mysql.jdbc.Driver
VitroConnection.DataSource.validationQuery = SELECT 1 # VitroConnection.DataSource.validationQuery = SELECT 1
#
# Include sections between <precise-subquery></precise-subquery>
# 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 # 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 # languages.selectableLocales = en, es, fr
# Triple pattern fragments is a very fast, very simple means for querying a triple store. # Triple Pattern Fragments is a very fast, very simple means for querying a
# 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 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 # tpf.activeFlag = true

View file

@ -0,0 +1,21 @@
<settings xmlns="http://maven.apache.org/SETTINGS/1.1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.1.0 http://maven.apache.org/xsd/settings-1.1.0.xsd">
<profiles>
<profile>
<id>defaults</id>
<properties>
<app-name>vitro</app-name>
<vitro-dir>/usr/local/vitro/home</vitro-dir>
<tomcat-dir>/usr/local/tomcat</tomcat-dir>
<default-theme>vitro</default-theme>
</properties>
</profile>
</profiles>
<activeProfiles>
<activeProfile>defaults</activeProfile>
</activeProfiles>
</settings>

View file

@ -7,13 +7,13 @@
<groupId>org.vivoweb</groupId> <groupId>org.vivoweb</groupId>
<artifactId>vitro-installer-home</artifactId> <artifactId>vitro-installer-home</artifactId>
<version>1.11.2-SNAPSHOT</version> <version>1.12.0-SNAPSHOT</version>
<packaging>pom</packaging> <packaging>pom</packaging>
<parent> <parent>
<groupId>org.vivoweb</groupId> <groupId>org.vivoweb</groupId>
<artifactId>vitro-installer</artifactId> <artifactId>vitro-installer</artifactId>
<version>1.11.2-SNAPSHOT</version> <version>1.12.0-SNAPSHOT</version>
<relativePath>..</relativePath> <relativePath>..</relativePath>
</parent> </parent>

View file

@ -7,13 +7,13 @@
<groupId>org.vivoweb</groupId> <groupId>org.vivoweb</groupId>
<artifactId>vitro-installer</artifactId> <artifactId>vitro-installer</artifactId>
<version>1.11.2-SNAPSHOT</version> <version>1.12.0-SNAPSHOT</version>
<packaging>pom</packaging> <packaging>pom</packaging>
<parent> <parent>
<groupId>org.vivoweb</groupId> <groupId>org.vivoweb</groupId>
<artifactId>vitro-project</artifactId> <artifactId>vitro-project</artifactId>
<version>1.11.2-SNAPSHOT</version> <version>1.12.0-SNAPSHOT</version>
<relativePath>..</relativePath> <relativePath>..</relativePath>
</parent> </parent>

View file

@ -7,13 +7,13 @@
<groupId>org.vivoweb</groupId> <groupId>org.vivoweb</groupId>
<artifactId>vitro-installer-solr</artifactId> <artifactId>vitro-installer-solr</artifactId>
<version>1.11.0-SNAPSHOT</version> <version>1.12.0-SNAPSHOT</version>
<packaging>war</packaging> <packaging>war</packaging>
<parent> <parent>
<groupId>org.vivoweb</groupId> <groupId>org.vivoweb</groupId>
<artifactId>vitro-installer</artifactId> <artifactId>vitro-installer</artifactId>
<version>1.11.0-SNAPSHOT</version> <version>1.12.0-SNAPSHOT</version>
<relativePath>..</relativePath> <relativePath>..</relativePath>
</parent> </parent>

View file

@ -7,13 +7,13 @@
<groupId>org.vivoweb</groupId> <groupId>org.vivoweb</groupId>
<artifactId>vitro-installer-webapp</artifactId> <artifactId>vitro-installer-webapp</artifactId>
<version>1.11.2-SNAPSHOT</version> <version>1.12.0-SNAPSHOT</version>
<packaging>war</packaging> <packaging>war</packaging>
<parent> <parent>
<groupId>org.vivoweb</groupId> <groupId>org.vivoweb</groupId>
<artifactId>vitro-installer</artifactId> <artifactId>vitro-installer</artifactId>
<version>1.11.2-SNAPSHOT</version> <version>1.12.0-SNAPSHOT</version>
<relativePath>..</relativePath> <relativePath>..</relativePath>
</parent> </parent>

View file

@ -1,51 +1,8 @@
<Context> <!-- useHttpOnly="false" --> <Context> <!-- useHttpOnly="false" -->
<!--
# The 'home' property specifies the location of Vitro HOME.
# The system user used to run the Vitro web application must have write access
# to the parent directory of the directory defined in this property, if Vitro HOME
# does not already exist.
# If this directory already exists, the system user used to run the Vitro web application
# must have write access to this directory.
-->
<Environment <Environment
type="java.lang.String" type="java.lang.String"
name="vitro/home" name="vitro/home"
value="${vivo-dir}" override="true"/> value="${vitro-dir}" override="true"/>
<!--
# The name of the application (possibly not used).
-->
<Environment
type="java.lang.String"
name="vitro/appName"
value="vivo" override="true"/>
<!--
# The email address of the root user for the Vitro 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.
-->
<Environment
type="java.lang.String"
name="vitro/rootUserAddress"
value="vivo_root@mydomain.edu" override="true"/>
<!--
# 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/
-->
<Environment
type="java.lang.String"
name="vitro/defaultNamespace"
value="http://vivo.mydomain.edu/individual/" override="true"/>
<!-- Disable persist sessions on shut down.--> <!-- Disable persist sessions on shut down.-->
<Manager pathname="" /> <Manager pathname="" />

View file

@ -7,7 +7,7 @@
<groupId>org.vivoweb</groupId> <groupId>org.vivoweb</groupId>
<artifactId>vitro-project</artifactId> <artifactId>vitro-project</artifactId>
<version>1.11.2-SNAPSHOT</version> <version>1.12.0-SNAPSHOT</version>
<packaging>pom</packaging> <packaging>pom</packaging>
<name>Vitro</name> <name>Vitro</name>

View file

@ -7,13 +7,13 @@
<groupId>org.vivoweb</groupId> <groupId>org.vivoweb</groupId>
<artifactId>vitro-webapp</artifactId> <artifactId>vitro-webapp</artifactId>
<version>1.11.2-SNAPSHOT</version> <version>1.12.0-SNAPSHOT</version>
<packaging>war</packaging> <packaging>war</packaging>
<parent> <parent>
<groupId>org.vivoweb</groupId> <groupId>org.vivoweb</groupId>
<artifactId>vitro-project</artifactId> <artifactId>vitro-project</artifactId>
<version>1.11.2-SNAPSHOT</version> <version>1.12.0-SNAPSHOT</version>
<relativePath>..</relativePath> <relativePath>..</relativePath>
</parent> </parent>
@ -41,7 +41,7 @@
<dependency> <dependency>
<groupId>org.vivoweb</groupId> <groupId>org.vivoweb</groupId>
<artifactId>vitro-api</artifactId> <artifactId>vitro-api</artifactId>
<version>1.11.2-SNAPSHOT</version> <version>1.12.0-SNAPSHOT</version>
</dependency> </dependency>
</dependencies> </dependencies>
</project> </project>

View file

@ -26,7 +26,7 @@
<form method="POST" action="${formUrls.createPassword}" class="customForm" role="create password"> <form method="POST" action="${formUrls.createPassword}" class="customForm" role="create password">
<input type="hidden" name="user" value="${userAccount.emailAddress}" role="input" /> <input type="hidden" name="user" value="${userAccount.emailAddress}" role="input" />
<input type="hidden" name="key" value="${userAccount.passwordLinkExpiresHash}" role="input" /> <input type="hidden" name="key" value="${userAccount.emailKey}" role="input" />
<label for="new-password">${strings.new_password}<span class="requiredHint"> *</span></label> <label for="new-password">${strings.new_password}<span class="requiredHint"> *</span></label>
<input type="password" name="newPassword" value="${newPassword}" id="new-password" role="input" /> <input type="password" name="newPassword" value="${newPassword}" id="new-password" role="input" />

View file

@ -26,7 +26,7 @@
<section id="reset-password" role="region"> <section id="reset-password" role="region">
<form method="POST" action="${formUrls.resetPassword}" class="customForm" role="create password"> <form method="POST" action="${formUrls.resetPassword}" class="customForm" role="create password">
<input type="hidden" name="user" value="${userAccount.emailAddress}" /> <input type="hidden" name="user" value="${userAccount.emailAddress}" />
<input type="hidden" name="key" value="${userAccount.passwordLinkExpiresHash}" /> <input type="hidden" name="key" value="${userAccount.emailKey}" />
<label for="new-password">${strings.new_password}<span class="requiredHint"> *</span></label> <label for="new-password">${strings.new_password}<span class="requiredHint"> *</span></label>
<input type="password" name="newPassword" value="${newPassword}" id="new-password" role="input" /> <input type="password" name="newPassword" value="${newPassword}" id="new-password" role="input" />

View file

@ -183,16 +183,16 @@ name will be used as the label. -->
<#local url = statement.editUrl> <#local url = statement.editUrl>
<#if url?has_content> <#if url?has_content>
<#if propertyLocalName?contains("ARG_2000028")> <#if propertyLocalName?contains("ARG_2000028")>
<#if rangeUri?contains("Address")> <#if rangeUri?contains("Address") && statement.address??>
<#local url = url + "&addressUri=" + "${statement.address!}"> <#local url = url + "&addressUri=" + "${statement.address?url}">
<#elseif rangeUri?contains("Telephone") || rangeUri?contains("Fax")> <#elseif (rangeUri?contains("Telephone") || rangeUri?contains("Fax")) && statement.phone??>
<#local url = url + "&phoneUri=" + "${statement.phone!}"> <#local url = url + "&phoneUri=" + "${statement.phone?url}">
<#elseif rangeUri?contains("Work") || rangeUri?contains("Email")> <#elseif (rangeUri?contains("Work") || rangeUri?contains("Email")) && statement.email??>
<#local url = url + "&emailUri=" + "${statement.email!}"> <#local url = url + "&emailUri=" + "${statement.email?url}">
<#elseif rangeUri?contains("Name")> <#elseif rangeUri?contains("Name") && statement.fullName??>
<#local url = url + "&fullNameUri=" + "${statement.fullName!}"> <#local url = url + "&fullNameUri=" + "${statement.fullName?url}">
<#elseif rangeUri?contains("Title")> <#elseif rangeUri?contains("Title") && statement.title??>
<#local url = url + "&titleUri=" + "${statement.title!}"> <#local url = url + "&titleUri=" + "${statement.title?url}">
</#if> </#if>
</#if> </#if>
<@showEditLink propertyLocalName rangeUri url /> <@showEditLink propertyLocalName rangeUri url />

View file

@ -1,3 +1,3 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="${country}" lang="${country}">

View file

@ -4,7 +4,7 @@
<#import "lib-home-page.ftl" as lh> <#import "lib-home-page.ftl" as lh>
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="${country}">
<head> <head>
<#include "head.ftl"> <#include "head.ftl">
</head> </head>

View file

@ -3,7 +3,7 @@
<#import "lib-list.ftl" as l> <#import "lib-list.ftl" as l>
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="${country}">
<head> <head>
<#include "head.ftl"> <#include "head.ftl">
</head> </head>