Revert "revert deployment changes (#217)" (#218)

This reverts commit e4e0a7d061.
This commit is contained in:
Ralph O'Flinn 2021-03-24 07:12:31 -05:00 committed by GitHub
parent e4e0a7d061
commit eb61192d11
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 409 additions and 135 deletions

View file

@ -24,6 +24,7 @@ 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;
@ -45,6 +46,8 @@ public class ApplicationSetup implements ServletContextListener {
this.vitroHomeDir = VitroHomeDirectory.find(ctx);
ss.info(this, vitroHomeDir.getDiscoveryMessage());
this.vitroHomeDir.populate();
locateApplicationConfigFile();
loadApplicationConfigFile();
createConfigurationBeanLoader();
@ -63,11 +66,19 @@ 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("'" + path + "' does not exist.");
throw new IllegalStateException("Neither '" + APPLICATION_SETUP_PATH + "' nor '" +
APPLICATION_SETUP_DEFAULT_PATH + "' were found in " +
this.vitroHomeDir.getPath());
}
if (!Files.isReadable(path)) {
throw new IllegalStateException("Can't read '" + path + "'");
throw new IllegalStateException("No readable '" + APPLICATION_SETUP_PATH + "' nor '" +
APPLICATION_SETUP_DEFAULT_PATH + "' files were found in " +
this.vitroHomeDir.getPath());
}
this.configFile = path;
}

View file

@ -4,25 +4,46 @@ 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 the Vitro home directory.
* Encapsulates some of the info relating to and initializes 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(),
@ -52,6 +73,219 @@ 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<String, String> digest = untar(vhdDir);
writeDigest(digest);
}
/**
* A non-destructive untar process that returns checksum digest of tarred files.
*
* Checksum digest can be manually created with the following command.
*
* `find /vivo/home -type f | cut -c3- | grep -E '^bin/|^config/|^rdf/' | xargs md5sum > /vivo/home/digest.md5`
*
* @param destination VIVO home directory
* @return digest of each files checksum
*/
private Map<String, String> untar(File destination) {
log.info("Syncing VIVO home at: " + destination.getPath());
Map<String, String> digest = new HashMap<>();
Map<String, String> storedDigest = loadDigest();
TarArchiveEntry tarEntry;
try (
InputStream homeDirTar = getHomeDirTar();
TarArchiveInputStream tarInput = new TarArchiveInputStream(homeDirTar);
) {
while ((tarEntry = tarInput.getNextTarEntry()) != null) {
// Use the example configurations
String outFilename = tarEntry.getName().replace("example.", "");
File outFile = new File(destination, outFilename);
// Is the entry a directory?
if (tarEntry.isDirectory()) {
if (!outFile.exists()) {
outFile.mkdirs();
}
} else {
// Entry is a File
boolean write = true;
// reading bytes into memory to avoid having to unreliably reset stream
byte[] bytes = IOUtils.toByteArray(tarInput);
String newFileChecksum = checksum(bytes);
digest.put(outFilename, newFileChecksum);
// if file already exists and stored digest contains the file,
// check to determine if it has changed
if (outFile.exists() && storedDigest.containsKey(outFilename)) {
String existingFileChecksum = checksum(outFile);
// if file has not changed in home and is not the same as new file, overwrite
write = storedDigest.get(outFilename).equals(existingFileChecksum)
&& !existingFileChecksum.equals(newFileChecksum);
}
if (write) {
outFile.getParentFile().mkdirs();
try (
InputStream is = new ByteArrayInputStream(bytes);
FileOutputStream fos = new FileOutputStream(outFile);
) {
IOUtils.copy(is, fos);
log.info(outFile.getAbsolutePath() + " source has changed and has not been "
+ "edited in home, updated file has been copied to home directory.");
}
} else {
log.debug(outFile.getAbsolutePath() + " has been preserved.");
}
}
}
} catch (IOException | NoSuchAlgorithmException e) {
throw new RuntimeException("Error creating home directory!", e);
}
return digest;
}
/**
* Load checksum digest of VIVO home directory.
*
* @return checksum digest
*/
private Map<String, String> loadDigest() {
File storedDigest = new File(getPath().toFile(), DIGEST_FILE_NAME);
if (storedDigest.exists() && storedDigest.isFile()) {
log.info("Reading VIVO home digest: " + storedDigest.getPath());
try {
return FileUtils
.readLines(storedDigest, StandardCharsets.UTF_8)
.stream()
.filter(CHECKSUM_PATTERN.asPredicate())
.map(this::split)
.collect(Collectors.toMap(this::checksumFile, this::checksumValue));
} catch (IOException e) {
throw new RuntimeException("Error reading VIVO home checksum digest!", e);
}
}
log.info("VIVO home digest not found: " + storedDigest.getPath());
return new HashMap<>();
}
/**
* Write VIVO home checksum digest following md5 format; `<checksum> *<file>`.
*
* @param digest checksum digest to write
*/
private void writeDigest(Map<String, String> digest) {
File storedDigest = new File(getPath().toFile(), DIGEST_FILE_NAME);
try (
FileOutputStream fos = new FileOutputStream(storedDigest);
OutputStreamWriter osw = new OutputStreamWriter(fos);
) {
for (Map.Entry<String, String> entry : digest.entrySet()) {
String filename = entry.getKey();
String checksum = entry.getValue();
osw.write(String.format("%s *%s\n", checksum, filename));
}
} catch (IOException e) {
throw new RuntimeException("Error writing home directory checksum digest!", e);
}
log.info("VIVO home digest created: " + storedDigest.getPath());
}
/**
* Split checksum.
*
* @param checksum checksum delimited by space and asterisks `<checksum> *<file>`
* @return split checksum
*/
private String[] split(String checksum) {
return checksum.split("\\s+");
}
/**
* Get value from split checksum.
*
* @param checksum split checksum
* @return checksum value
*/
private String checksumValue(String[] checksum) {
return checksum[0];
}
/**
* Return file from split checksum.
*
* @param checksum split checksum
* @return filename
*/
private String checksumFile(String[] checksum) {
return checksum[1].substring(1);
}
/**
* Get md5 checksum from file.
*
* @param file file
* @return md5 checksum as string
* @throws IOException
* @throws NoSuchAlgorithmException
*/
private String checksum(File file) throws IOException, NoSuchAlgorithmException {
return checksum(FileUtils.readFileToByteArray(file));
}
/**
* Get md5 checksum from bytes.
*
* @param bytes bytes from file
* @return md5 checksum as string
* @throws NoSuchAlgorithmException
*/
private String checksum(byte[] bytes) throws NoSuchAlgorithmException {
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(bytes);
// bytes to hex
StringBuilder result = new StringBuilder();
for (byte b : md.digest()) {
result.append(String.format("%02x", b));
}
return result.toString();
}
/**
* Get prepacked VIVO home tar file as input stream.
*
* @return input stream of VIVO home tar file
*/
private InputStream getHomeDirTar() {
String tarLocation = "/WEB-INF/resources/home-files/vivo-home.tar";
InputStream tar = ctx.getResourceAsStream(tarLocation);
if (tar == null) {
log.error("Application home tar not found in: " + tarLocation);
throw new RuntimeException("Application home tar not found in: " + tarLocation);
}
return tar;
}
/**
* Find something that specifies the location of the Vitro home directory.
* Look in the JDNI environment, the system properties, and the
@ -92,23 +326,12 @@ public class VitroHomeDirectory {
}
public void getVhdFromJndi() {
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);
}
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));
}
private void getVhdFromSystemProperties() {

View file

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

View file

@ -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,18 +69,17 @@ public class ConfigurationPropertiesSetup implements ServletContextListener {
File vitroHomeDirConfig = new File(vitroHomeDir.getPath()
.concat(File.separator).concat("config"));
String rpfLocation = findMultipleRuntimePropertiesFiles(
vitroHomeDir, vitroHomeDirConfig);
File runtimePropertiesFile = locateRuntimePropertiesFile(
vitroHomeDir, vitroHomeDirConfig, ss);
vitroHomeDirConfig, ss);
stream = new FileInputStream(runtimePropertiesFile);
Map<String, String> preempts = createPreemptiveProperties(
VHD_CONFIGURATION_PROPERTY, vitroHomeDir, RP_MULTIPLE, rpfLocation);
VHD_CONFIGURATION_PROPERTY, vitroHomeDir);
ConfigurationPropertiesImpl bean = new ConfigurationPropertiesImpl(
stream, preempts, new BuildProperties(ctx).getMap());
stream, preempts,
new BuildProperties(ctx).getMap(),
new ContextProperties().getMap());
ConfigurationProperties.setBean(ctx, bean);
ss.info(this, "Loaded " + bean.getPropertyMap().size()
@ -99,53 +98,36 @@ public class ConfigurationPropertiesSetup implements ServletContextListener {
}
}
private String findMultipleRuntimePropertiesFiles(File vitroHomeDir,
File vitroHomeDirConfig) {
private File locateRuntimePropertiesFile(File vitroHomeDirConfig, StartupStatus ss) {
File rpf = new File(vitroHomeDir, FILE_RUNTIME_PROPERTIES);
File rpfc = new File(vitroHomeDirConfig, FILE_RUNTIME_PROPERTIES);
// First look for the user-customized runtime.properties
File rpf = new File(vitroHomeDirConfig, FILE_RUNTIME_PROPERTIES);
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 + "'");
}
}
// Have we found a suitable runtime.properties file?
if (!rpf.exists() || !rpf.isFile() || !rpf.canRead()) {
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 not... look for the default runtime.properties
rpf = new File(vitroHomeDirConfig, FILE_DEFAULT_RUNTIME_PROPERTIES);
}
if (!rpf.isFile()) {
throw new IllegalStateException("'" + rpf.getPath()
+ "' is not a file.");
if (!rpf.exists() || !rpf.isFile()) {
throw new IllegalStateException("Neither '" + FILE_RUNTIME_PROPERTIES + "' nor '" +
FILE_DEFAULT_RUNTIME_PROPERTIES + "' were found in " +
vitroHomeDirConfig.getAbsolutePath());
}
if (!rpf.canRead()) {
throw new IllegalStateException("Cannot read '" + rpf.getPath()
+ "'.");
throw new IllegalStateException("No readable '" + FILE_RUNTIME_PROPERTIES + "' nor '" +
FILE_DEFAULT_RUNTIME_PROPERTIES + "' files were found in " +
vitroHomeDirConfig.getAbsolutePath());
}
ss.info(this, "Loading runtime properties from '" + rpf.getPath() + "'");
return rpf;
}
private Map<String, String> createPreemptiveProperties(
String propertyVitroHome, File vitroHomeDir, String propertyRpfMultiple,
String rpfLocation) {
String propertyVitroHome, File vitroHomeDir) {
Map<String, String> map = new HashMap<String, String>();
map.put(propertyVitroHome, vitroHomeDir.getAbsolutePath());
map.put(propertyRpfMultiple, rpfLocation);
return map;
}

View file

@ -48,7 +48,6 @@ public class ConfigurationPropertiesSmokeTests implements
StartupStatus ss = StartupStatus.getBean(ctx);
checkDefaultNamespace(ctx, props, ss);
checkMultipleRPFs(ctx, props, ss);
checkLanguages(ctx, props, ss);
checkEncryptionParameters(props, ss);
@ -86,32 +85,6 @@ 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:
*

View file

@ -0,0 +1,76 @@
/* $This file is distributed under the terms of the license in LICENSE$ */
package edu.cornell.mannlib.vitro.webapp.config;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Obtains and provides the properties from the web application's context.xml
*
* @author awoods
* @since 2020-10-23
*/
public class ContextProperties {
private static final Log log = LogFactory.getLog(ContextProperties.class);
private static final String DEFAULT_NAMESPACE_JNDI_PATH = "java:comp/env/vitro/defaultNamespace";
private static final String ROOT_USER_ADDRESS_JNDI_PATH = "java:comp/env/vitro/rootUserAddress";
private static final String APP_NAME_JNDI_PATH = "java:comp/env/vitro/appName";
private static final String DEFAULT_NAMESPACE_KEY = "Vitro.defaultNamespace";
private static final String ROOT_USER_ADDRESS_KEY = "rootUser.emailAddress";
private static final String APP_NAME_KEY = "app-name";
private final Map<String, String> propertyMap;
public ContextProperties() {
Map<String, String> map = new HashMap<>();
// Find default namespace
map.put(DEFAULT_NAMESPACE_KEY, findJndiProperty(DEFAULT_NAMESPACE_JNDI_PATH));
// Find root user email address
map.put(ROOT_USER_ADDRESS_KEY, findJndiProperty(ROOT_USER_ADDRESS_JNDI_PATH));
// Find application name
map.put(APP_NAME_KEY, findJndiProperty(APP_NAME_JNDI_PATH));
propertyMap = Collections.unmodifiableMap(map);
}
public static String findJndiProperty(String jndiProperty) {
try {
return (String) new InitialContext().lookup(jndiProperty);
} catch (NamingException e) {
log.error("Unable to find name in JNDI: " + jndiProperty, e);
StringBuilder msg = new StringBuilder("\n====================\n");
msg.append("Error loading JNDI property: ");
msg.append(jndiProperty);
msg.append("\n");
msg.append("\tAn application context XML file (named after deployed war file, e.g. vivo.xml) ");
msg.append("must be placed in servlet container.\n");
msg.append("\tFor Tomcat, see documentation for location of file: \n");
msg.append("\t\thttps://tomcat.apache.org/tomcat-9.0-doc/config/context.html#Defining_a_context \n");
msg.append("\tThe common location on the server is: $CATALINA_BASE/conf/[enginename]/[hostname]/ \n");
msg.append("\t\te.g. /var/lib/tomcat9/conf/Catalina/localhost/vivo.xml\n");
msg.append("\tAn example 'context.xml' file is in the META-INF directory of this project.\n");
msg.append("====================\n");
throw new RuntimeException(msg.toString(), e);
}
}
public Map<String, String> getMap() {
return this.propertyMap;
}
}

View file

@ -202,18 +202,23 @@ 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 'developer.properties' file: "
ss.info(this, "Loaded the '" + dsFile.getName() + "' file: "
+ devSettings);
} catch (FileNotFoundException e) {
ss.info(this, "'developer.properties' file does not exist.");
ss.info(this, "Neither 'developer.properties' nor 'default.developer.properties' " +
"files exist.");
} catch (Exception e) {
ss.warning(this,
"Failed to load the 'developer.properties' file.", e);
"Failed to load the '" + dsFile.getAbsolutePath() + "' file.", e);
}
}

View file

@ -9,19 +9,6 @@
#
# -----------------------------------------------------------------------------
#
# 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"
@ -70,13 +57,6 @@ 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.

View file

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

View file

@ -1,8 +1,51 @@
<Context> <!-- useHttpOnly="false" -->
<!--
# The 'home' property specifies the location of Vitro HOME.
# The system user used to run the Vitro web application must have write access
# to the parent directory of the directory defined in this property, if Vitro HOME
# does not already exist.
# If this directory already exists, the system user used to run the Vitro web application
# must have write access to this directory.
-->
<Environment
type="java.lang.String"
name="vitro/home"
value="${vitro-dir}" override="true"/>
value="${vivo-dir}" override="true"/>
<!--
# The name of the application (possibly not used).
-->
<Environment
type="java.lang.String"
name="vitro/appName"
value="vivo" override="true"/>
<!--
# The email address of the root user for the Vitro application. The password
# for this user is initially set to "rootPassword", but you will be asked to
# change the password the first time you log in.
-->
<Environment
type="java.lang.String"
name="vitro/rootUserAddress"
value="vivo_root@mydomain.edu" override="true"/>
<!--
# This namespace will be used when generating URIs for objects created in the
# editor. In order to serve linked data, the default namespace must be composed
# as follows (optional elements in parentheses):
#
# scheme + server_name (+ port) (+ servlet_context) + "/individual/"
#
# For example, Cornell's default namespace is:
#
# http://vivo.cornell.edu/individual/
-->
<Environment
type="java.lang.String"
name="vitro/defaultNamespace"
value="http://vivo.mydomain.edu/individual/" override="true"/>
<!-- Disable persist sessions on shut down.-->
<Manager pathname="" />