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