diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/config/ConfigurationPropertiesSmokeTests.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/config/ConfigurationPropertiesSmokeTests.java index 4cb9d285b..604881b2c 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/config/ConfigurationPropertiesSmokeTests.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/config/ConfigurationPropertiesSmokeTests.java @@ -3,16 +3,8 @@ package edu.cornell.mannlib.vitro.webapp.config; import java.io.File; -import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; -import java.util.Properties; import javax.servlet.ServletContext; import javax.servlet.ServletContextEvent; @@ -34,19 +26,12 @@ public class ConfigurationPropertiesSmokeTests implements .getLog(ConfigurationPropertiesSmokeTests.class); private static final String PROPERTY_HOME_DIRECTORY = "vitro.home"; - private static final String PROPERTY_DB_URL = "VitroConnection.DataSource.url"; - private static final String PROPERTY_DB_USERNAME = "VitroConnection.DataSource.username"; - private static final String PROPERTY_DB_PASSWORD = "VitroConnection.DataSource.password"; - private static final String PROPERTY_DB_DRIVER_CLASS_NAME = "VitroConnection.DataSource.driver"; - private static final String PROPERTY_DB_TYPE = "VitroConnection.DataSource.dbtype"; private static final String PROPERTY_DEFAULT_NAMESPACE = "Vitro.defaultNamespace"; private static final String PROPERTY_LANGUAGE_BUILD = "languages.addToBuild"; private static final String PROPERTY_LANGUAGE_SELECTABLE = "languages.selectableLocales"; private static final String PROPERTY_LANGUAGE_FORCE = "languages.forceLocale"; private static final String PROPERTY_LANGUAGE_FILTER = "RDFService.languageFilter"; - private static final String DEFAULT_DB_DRIVER_CLASS = "com.mysql.jdbc.Driver"; - @Override public void contextInitialized(ServletContextEvent sce) { ServletContext ctx = sce.getServletContext(); @@ -54,7 +39,6 @@ public class ConfigurationPropertiesSmokeTests implements StartupStatus ss = StartupStatus.getBean(ctx); checkHomeDirectory(ctx, props, ss); - checkDatabaseConnection(ctx, props, ss); checkDefaultNamespace(ctx, props, ss); checkLanguages(props, ss); } @@ -94,133 +78,7 @@ public class ConfigurationPropertiesSmokeTests implements } } - /** - * Confirm that the URL, Username and Password have been specified for the - * Database connection. Confirm that we can load the database driver. - * Confirm that we can connect to the database using those properties. - */ - private void checkDatabaseConnection(ServletContext ctx, - ConfigurationProperties props, StartupStatus ss) { - String url = props.getProperty(PROPERTY_DB_URL); - if (url == null || url.isEmpty()) { - ss.fatal(this, "runtime.properties does not contain a value for '" - + PROPERTY_DB_URL + "'"); - return; - } - String username = props.getProperty(PROPERTY_DB_USERNAME); - if (username == null || username.isEmpty()) { - ss.fatal(this, "runtime.properties does not contain a value for '" - + PROPERTY_DB_USERNAME + "'"); - return; - } - String password = props.getProperty(PROPERTY_DB_PASSWORD); - if (password == null || password.isEmpty()) { - ss.fatal(this, "runtime.properties does not contain a value for '" - + PROPERTY_DB_PASSWORD + "'"); - return; - } - - String driverClassName = props - .getProperty(PROPERTY_DB_DRIVER_CLASS_NAME); - if (driverClassName == null) { - try { - Class.forName(DEFAULT_DB_DRIVER_CLASS).newInstance(); - } catch (Exception e) { - ss.fatal(this, "The default Database Driver failed to load. " - + "The driver class is '" + DEFAULT_DB_DRIVER_CLASS - + "'", e); - return; - } - } else { - try { - Class.forName(driverClassName).newInstance(); - } catch (Exception e) { - ss.fatal(this, "The Database Driver failed to load. " - + "The driver class was set by " - + PROPERTY_DB_DRIVER_CLASS_NAME + " to be '" - + driverClassName + "'", e); - return; - } - } - - Properties connectionProps = new Properties(); - connectionProps.put("user", username); - connectionProps.put("password", password); - - try (Connection conn = DriverManager - .getConnection(url, connectionProps)) { - // Just open the connection and close it. - } catch (SQLException e) { - ss.fatal(this, "Can't connect to the database: " + PROPERTY_DB_URL - + "='" + url + "', " + PROPERTY_DB_USERNAME + "='" - + username + "'", e); - return; - } - - String dbType = props.getProperty(PROPERTY_DB_TYPE, "MySQL"); - checkForPropertHandlingOfUnicodeCharacters(url, connectionProps, ss, - dbType); - } - - private void checkForPropertHandlingOfUnicodeCharacters(String url, - Properties connectionProps, StartupStatus ss, String dbType) { - String testString = "ABC\u00CE\u0123"; - - try (Connection conn = DriverManager - .getConnection(url, connectionProps); - Statement stmt = conn.createStatement()) { - - // Create the temporary table. - stmt.executeUpdate("CREATE TEMPORARY TABLE smoke_test (contents varchar(100))"); - - // Write the test string, encoding in UTF-8 on the way in. - try (PreparedStatement pstmt = conn - .prepareStatement("INSERT INTO smoke_test values ( ? )")) { - pstmt.setBytes(1, testString.getBytes("UTF-8")); - pstmt.executeUpdate(); - } catch (UnsupportedEncodingException e1) { - e1.printStackTrace(); - } - - // Read it back as a String. Does the database decode it properly? - ResultSet rs = stmt.executeQuery("SELECT * FROM smoke_test"); - if (!rs.next()) { - throw new SQLException( - "Query of temporary table returned 0 rows."); - } - String storedValue = rs.getString(1); - if (!testString.equals(storedValue)) { - String message = "The database does not store Unicode " - + "characters correctly. The test inserted \"" - + showUnicode(testString) - + "\", but the query returned \"" - + showUnicode(storedValue) - + "\". Is the character encoding correctly " - + "set on the database?"; - if ("MySQL".equals(dbType)) { - // For MySQL, we know that this is a configuration problem. - ss.fatal(this, message); - } else { - // For other databases, it might not be. - ss.warning(this, message); - } - } - } catch (SQLException e) { - ss.fatal(this, "Failed to check handling of Unicode characters", e); - } - } - - /** - * Display the hex codes for a String. - */ - private String showUnicode(String testString) { - StringBuilder u = new StringBuilder(); - for (char c : testString.toCharArray()) { - u.append(String.format("\\u%04x", c & 0x0000FFFF)); - } - return u.toString(); - } - + /** * Confirm that the default namespace is specified and a syntactically valid * URI. It should also end with "/individual/". diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/modelaccess/ConfigurationModelMakerFactory.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/modelaccess/ConfigurationModelMakerFactory.java new file mode 100644 index 000000000..f0ae53616 --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/modelaccess/ConfigurationModelMakerFactory.java @@ -0,0 +1,42 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.modelaccess; + +import static edu.cornell.mannlib.vitro.webapp.modelaccess.ModelNames.DISPLAY; +import static edu.cornell.mannlib.vitro.webapp.modelaccess.ModelNames.DISPLAY_DISPLAY; +import static edu.cornell.mannlib.vitro.webapp.modelaccess.ModelNames.DISPLAY_TBOX; +import static edu.cornell.mannlib.vitro.webapp.modelaccess.ModelNames.USER_ACCOUNTS; +import static edu.cornell.mannlib.vitro.webapp.rdfservice.impl.RDFServiceUtils.WhichService.CONFIGURATION; + +import com.hp.hpl.jena.rdf.model.ModelMaker; + +import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.RDFServiceUtils.WhichService; + +/** + * Common functionality among the Configuration-based ModelMakerFactorys + */ +public abstract class ConfigurationModelMakerFactory implements + ModelMakerFactory { + + /** + * A list of all Configuration models, in case the implementation wants to + * add memory-mapping. + */ + protected static final String[] CONFIGURATION_MODELS = { DISPLAY, + DISPLAY_TBOX, DISPLAY_DISPLAY, USER_ACCOUNTS }; + + /** + * These decorators are added to a Configuration ModelMaker, regardless of + * the source. + */ + protected ModelMaker addConfigurationDecorators(ModelMaker sourceMM) { + // No decorators yet. + return sourceMM; + } + + @Override + public WhichService whichModelMaker() { + return CONFIGURATION; + } + +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/modelaccess/ContentModelMakerFactory.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/modelaccess/ContentModelMakerFactory.java new file mode 100644 index 000000000..3cbe0344f --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/modelaccess/ContentModelMakerFactory.java @@ -0,0 +1,65 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.modelaccess; + +import static edu.cornell.mannlib.vitro.webapp.modelaccess.ModelNames.ABOX_ASSERTIONS; +import static edu.cornell.mannlib.vitro.webapp.modelaccess.ModelNames.ABOX_INFERENCES; +import static edu.cornell.mannlib.vitro.webapp.modelaccess.ModelNames.ABOX_UNION; +import static edu.cornell.mannlib.vitro.webapp.modelaccess.ModelNames.APPLICATION_METADATA; +import static edu.cornell.mannlib.vitro.webapp.modelaccess.ModelNames.FULL_ASSERTIONS; +import static edu.cornell.mannlib.vitro.webapp.modelaccess.ModelNames.FULL_INFERENCES; +import static edu.cornell.mannlib.vitro.webapp.modelaccess.ModelNames.FULL_UNION; +import static edu.cornell.mannlib.vitro.webapp.modelaccess.ModelNames.TBOX_ASSERTIONS; +import static edu.cornell.mannlib.vitro.webapp.modelaccess.ModelNames.TBOX_INFERENCES; +import static edu.cornell.mannlib.vitro.webapp.modelaccess.ModelNames.TBOX_UNION; +import static edu.cornell.mannlib.vitro.webapp.rdfservice.impl.RDFServiceUtils.WhichService.CONTENT; + +import com.hp.hpl.jena.rdf.model.ModelMaker; + +import edu.cornell.mannlib.vitro.webapp.modelaccess.adapters.NamedDefaultModelMaker; +import edu.cornell.mannlib.vitro.webapp.modelaccess.adapters.UnionModelsModelMaker; +import edu.cornell.mannlib.vitro.webapp.modelaccess.adapters.UnionModelsModelMaker.UnionSpec; +import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.RDFServiceUtils.WhichService; + +/** + * Common functionality among the Content-based ModelMakerFactorys + */ +public abstract class ContentModelMakerFactory implements ModelMakerFactory { + + /** + * These are the small content models that we want to keep in memory. + */ + protected static final String[] SMALL_CONTENT_MODELS = { + APPLICATION_METADATA, TBOX_ASSERTIONS, TBOX_INFERENCES }; + + + private static final UnionSpec[] CONTENT_UNIONS = new UnionSpec[] { + UnionSpec.base(ABOX_ASSERTIONS).plus(ABOX_INFERENCES) + .yields(ABOX_UNION), + UnionSpec.base(TBOX_ASSERTIONS).plus(TBOX_INFERENCES) + .yields(TBOX_UNION), + UnionSpec.base(ABOX_ASSERTIONS).plus(TBOX_ASSERTIONS) + .yields(FULL_ASSERTIONS), + UnionSpec.base(ABOX_INFERENCES).plus(TBOX_INFERENCES) + .yields(FULL_INFERENCES) }; + + private static final String CONTENT_DEFAULT_MODEL_NAME = FULL_UNION; + + /** + * These decorations are added to a Content ModelMaker, regardless of the + * source. + * + * Create the union models and full models. Use the default model as the + * full union. + */ + protected ModelMaker addContentDecorators(ModelMaker sourceMM) { + ModelMaker unions = new UnionModelsModelMaker(sourceMM, CONTENT_UNIONS); + return new NamedDefaultModelMaker(unions, CONTENT_DEFAULT_MODEL_NAME); + } + + @Override + public WhichService whichModelMaker() { + return CONTENT; + } + +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/modelaccess/ModelMakerFactory.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/modelaccess/ModelMakerFactory.java new file mode 100644 index 000000000..1156e23b6 --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/modelaccess/ModelMakerFactory.java @@ -0,0 +1,34 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.modelaccess; + +import com.hp.hpl.jena.rdf.model.ModelMaker; + +import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService; +import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.RDFServiceUtils.WhichService; + +/** + * Get model makers for long and short-term use. + */ +public interface ModelMakerFactory { + + /** + * Get a model maker that is suitable for long-term use. + */ + ModelMaker getModelMaker(RDFService longTermRdfService); + + /** + * Get a model maker that should not be left idle for long periods of time. + * + * Because it is based (at least in part) on a short-term RDFService, it + * should not be stored in the context or the session, but should be deleted + * at the end of the request. + */ + ModelMaker getShortTermModelMaker(RDFService shortTermRdfService); + + /** + * Is this factory configured to provide CONTENT models or CONFIGURATION models? + */ + WhichService whichModelMaker(); + +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/modelaccess/ModelMakerUtils.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/modelaccess/ModelMakerUtils.java new file mode 100644 index 000000000..4e5d7716c --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/modelaccess/ModelMakerUtils.java @@ -0,0 +1,104 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.modelaccess; + +import static edu.cornell.mannlib.vitro.webapp.rdfservice.impl.RDFServiceUtils.WhichService.CONFIGURATION; +import static edu.cornell.mannlib.vitro.webapp.rdfservice.impl.RDFServiceUtils.WhichService.CONTENT; + +import javax.servlet.ServletContext; + +import com.hp.hpl.jena.rdf.model.ModelMaker; + +import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService; +import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.RDFServiceUtils; +import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.RDFServiceUtils.WhichService; + +/** + * Convenience methods for obtaining the ModelMakerFactories. + */ +public class ModelMakerUtils { + public static final String ATTRIBUTE_BASE = ModelMakerUtils.class.getName(); + + public static void setContentModelMakerFactory(ServletContext ctx, + ModelMakerFactory mmFactory) { + if (ctx == null) { + throw new NullPointerException("ctx may not be null."); + } + if (mmFactory == null) { + throw new NullPointerException("mmFactory may not be null."); + } + if (mmFactory.whichModelMaker() != CONTENT) { + throw new IllegalArgumentException( + "mmFactory must be a CONTENT ModelMakerFactory"); + } + ctx.setAttribute(attributeName(CONTENT), mmFactory); + } + + public static void setConfigurationModelMakerFactory(ServletContext ctx, + ModelMakerFactory mmFactory) { + if (ctx == null) { + throw new NullPointerException("ctx may not be null."); + } + if (mmFactory == null) { + throw new NullPointerException("mmFactory may not be null."); + } + if (mmFactory.whichModelMaker() != CONFIGURATION) { + throw new IllegalArgumentException( + "mmFactory must be a CONFIGURATION ModelMakerFactory"); + } + ctx.setAttribute(attributeName(CONFIGURATION), mmFactory); + } + + public static ModelMaker getModelMaker(ServletContext ctx, + WhichService which) { + if (ctx == null) { + throw new NullPointerException("ctx may not be null."); + } + if (which == null) { + throw new NullPointerException("which may not be null."); + } + return getFactory(ctx, which).getModelMaker( + RDFServiceUtils.getRDFServiceFactory(ctx) + .getShortTermRDFService()); + } + + public static ModelMaker getShortTermModelMaker(ServletContext ctx, + RDFService shortTermRdfService, WhichService which) { + if (ctx == null) { + throw new NullPointerException("ctx may not be null."); + } + if (shortTermRdfService == null) { + throw new NullPointerException( + "shortTermRdfService may not be null."); + } + if (which == null) { + throw new NullPointerException("which may not be null."); + } + + return getFactory(ctx, which).getShortTermModelMaker( + shortTermRdfService); + } + + private static ModelMakerFactory getFactory(ServletContext ctx, + WhichService which) { + Object attribute = ctx.getAttribute(attributeName(which)); + if (attribute instanceof ModelMakerFactory) { + return (ModelMakerFactory) attribute; + } else { + throw new IllegalStateException("Expected a ModelMakerFactory at '" + + attributeName(which) + "', but found " + attribute); + } + } + + private static String attributeName(WhichService which) { + return ATTRIBUTE_BASE + "-" + which; + } + + /** + * No need for an instance - all methods are static. + */ + private ModelMakerUtils() { + // Nothing to instantiate. + } + +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/rdfservice/impl/jena/sdb/RDFServiceFactorySDB.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/rdfservice/impl/jena/sdb/RDFServiceFactorySDB.java index 4c12f5374..57afdd0c8 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/rdfservice/impl/jena/sdb/RDFServiceFactorySDB.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/rdfservice/impl/jena/sdb/RDFServiceFactorySDB.java @@ -2,8 +2,6 @@ package edu.cornell.mannlib.vitro.webapp.rdfservice.impl.jena.sdb; -import java.sql.SQLException; - import javax.sql.DataSource; import org.apache.commons.logging.Log; diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/rdfservice/impl/jena/tdb/RDFServiceFactoryTDB.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/rdfservice/impl/jena/tdb/RDFServiceFactoryTDB.java deleted file mode 100644 index 1cfcc4ed4..000000000 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/rdfservice/impl/jena/tdb/RDFServiceFactoryTDB.java +++ /dev/null @@ -1,51 +0,0 @@ -/* $This file is distributed under the terms of the license in /doc/license.txt$ */ - -package edu.cornell.mannlib.vitro.webapp.rdfservice.impl.jena.tdb; - -import java.io.IOException; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import edu.cornell.mannlib.vitro.webapp.rdfservice.ChangeListener; -import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService; -import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFServiceException; -import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFServiceFactory; - -/** - * TODO - */ -public class RDFServiceFactoryTDB implements RDFServiceFactory { - private static final Log log = LogFactory - .getLog(RDFServiceFactoryTDB.class); - - - private final RDFServiceTDB service; - - public RDFServiceFactoryTDB(String directoryPath) throws IOException { - this.service = new RDFServiceTDB(directoryPath); - } - - @Override - public RDFService getRDFService() { - return service; - } - - @Override - public RDFService getShortTermRDFService() { - return service; - } - - @Override - public void registerListener(ChangeListener changeListener) - throws RDFServiceException { - service.registerListener(changeListener); - } - - @Override - public void unregisterListener(ChangeListener changeListener) - throws RDFServiceException { - service.unregisterListener(changeListener); - } - -} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/rdfservice/impl/jena/tdb/RDFServiceTDB.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/rdfservice/impl/jena/tdb/RDFServiceTDB.java index 44028a43f..2f9e7890c 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/rdfservice/impl/jena/tdb/RDFServiceTDB.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/rdfservice/impl/jena/tdb/RDFServiceTDB.java @@ -20,7 +20,7 @@ import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFServiceException; import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.jena.RDFServiceJena; /** - * TODO + * An implementation that is based on Jena TDB. */ public class RDFServiceTDB extends RDFServiceJena { private static final Log log = LogFactory.getLog(RDFServiceTDB.class); @@ -79,4 +79,10 @@ public class RDFServiceTDB extends RDFServiceJena { } } + @Override + public void close() { + if (this.dataset != null) { + dataset.close(); + } + } } diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/JenaPersistentDataSourceSetup.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/JenaPersistentDataSourceSetup.java deleted file mode 100644 index 2dfa9caeb..000000000 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/JenaPersistentDataSourceSetup.java +++ /dev/null @@ -1,163 +0,0 @@ -/* $This file is distributed under the terms of the license in /doc/license.txt$ */ - -package edu.cornell.mannlib.vitro.webapp.servlet.setup; - -import java.beans.PropertyVetoException; - -import javax.servlet.ServletContext; -import javax.servlet.ServletContextEvent; -import javax.servlet.ServletContextListener; -import javax.sql.DataSource; - -import org.apache.commons.lang.StringUtils; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import com.hp.hpl.jena.ontology.OntDocumentManager; -import com.mchange.v2.c3p0.ComboPooledDataSource; - -import edu.cornell.mannlib.vitro.webapp.config.ConfigurationProperties; - -/** - * Create connection to DB and DataSource, put them in the context. - */ -public class JenaPersistentDataSourceSetup implements ServletContextListener { - private static final Log log = LogFactory - .getLog(JenaPersistentDataSourceSetup.class.getName()); - - private static final String PROPERTY_DBTYPE = "VitroConnection.DataSource.dbtype"; - private static final String PROPERTY_JDBC_URL = "VitroConnection.DataSource.url"; - private static final String PROPERTY_DRIVER_CLASS = "VitroConnection.DataSource.driver"; - private static final String PROPERTY_PASSWORD = "VitroConnection.DataSource.password"; - private static final String PROPERTY_USERNAME = "VitroConnection.DataSource.username"; - private static final String PROPERTY_MAX_ACTIVE = "VitroConnection.DataSource.pool.maxActive"; - private static final String PROPERTY_MAX_IDLE = "VitroConnection.DataSource.pool.maxIdle"; - private static final String PROPERTY_VALIDATION_QUERY = "VitroConnection.DataSource.validationQuery"; - - private static final String DEFAULT_DBTYPE = "MySQL"; - private static final String DEFAULT_DRIVER_CLASS = "com.mysql.jdbc.Driver"; - private static final String DEFAULT_VALIDATION_QUERY = "SELECT 1"; - - private static final int DEFAULT_MAXACTIVE = 40; //ms - private static final int MINIMUM_MAXACTIVE = 20; //ms - private static final int DEFAULT_MAXIDLE = 10; //ms - - private static final boolean DEFAULT_TESTONBORROW = true; - private static final boolean DEFAULT_TESTONRETURN = true; - - private ConfigurationProperties configProps; - - private static ComboPooledDataSource ds; - - @Override - public void contextInitialized(ServletContextEvent sce) { - ServletContext ctx = sce.getServletContext(); - configProps = ConfigurationProperties.getBean(ctx); - - // we do not want to fetch imports when we wrap Models in OntModels - OntDocumentManager.getInstance().setProcessImports(false); - - JenaPersistentDataSourceSetup.ds = makeC3poDataSource(); - } - - private ComboPooledDataSource makeC3poDataSource() { - try { - ComboPooledDataSource cpds = new ComboPooledDataSource(); - cpds.setDriverClass(getDbDriverClassName()); - cpds.setJdbcUrl(getJdbcUrl()); - cpds.setUser(configProps.getProperty(PROPERTY_USERNAME)); - cpds.setPassword(configProps.getProperty(PROPERTY_PASSWORD)); - cpds.setMaxPoolSize(getMaxActive()); - cpds.setMinPoolSize(getMaxIdle()); - cpds.setMaxIdleTime(43200); // s - cpds.setMaxIdleTimeExcessConnections(300); - cpds.setAcquireIncrement(5); - cpds.setNumHelperThreads(6); - cpds.setTestConnectionOnCheckout(DEFAULT_TESTONBORROW); - cpds.setTestConnectionOnCheckin(DEFAULT_TESTONRETURN); - cpds.setPreferredTestQuery(getValidationQuery()); - return cpds; - } catch (PropertyVetoException pve) { - throw new RuntimeException(pve); - } - } - - private String getDbDriverClassName() { - return configProps.getProperty(PROPERTY_DRIVER_CLASS, - DEFAULT_DRIVER_CLASS); - } - - private String getDbType() { - return configProps.getProperty(PROPERTY_DBTYPE, DEFAULT_DBTYPE); - } - - private String getJdbcUrl() { - String url = configProps.getProperty(PROPERTY_JDBC_URL); - - // Ensure that MySQL handles unicode properly, else all kinds of - // horrible nastiness ensues. - if (DEFAULT_DBTYPE.equals(getDbType()) && !url.contains("?")) { - url += "?useUnicode=yes&characterEncoding=utf8"; - } - - return url; - } - - private String getValidationQuery() { - return configProps.getProperty(PROPERTY_VALIDATION_QUERY, - DEFAULT_VALIDATION_QUERY); - } - - private int getMaxActive() { - String maxActiveStr = configProps.getProperty(PROPERTY_MAX_ACTIVE); - if (StringUtils.isEmpty(maxActiveStr)) { - return DEFAULT_MAXACTIVE; - } - - int maxActive = DEFAULT_MAXACTIVE; - try { - maxActive = Integer.parseInt(maxActiveStr); - } catch (NumberFormatException nfe) { - log.error("Unable to parse connection pool maxActive setting " - + maxActiveStr + " as an integer"); - return DEFAULT_MAXACTIVE; - } - - if (maxActive >= MINIMUM_MAXACTIVE) { - return maxActive; - } - - log.warn("Specified value for " + PROPERTY_MAX_ACTIVE - + " is too low. Using minimum value of " + MINIMUM_MAXACTIVE); - return MINIMUM_MAXACTIVE; - } - - private int getMaxIdle() { - int maxIdleInt = Math.max(getMaxActive() / 4, DEFAULT_MAXIDLE); - String maxIdleStr = configProps.getProperty(PROPERTY_MAX_IDLE); - - if (StringUtils.isEmpty(maxIdleStr)) { - return maxIdleInt; - } - - try { - return Integer.parseInt(maxIdleStr); - } catch (NumberFormatException nfe) { - log.error("Unable to parse connection pool maxIdle setting " - + maxIdleStr + " as an integer"); - return maxIdleInt; - } - } - - public static DataSource getApplicationDataSource() { - return JenaPersistentDataSourceSetup.ds; - } - - @Override - public void contextDestroyed(ServletContextEvent sce) { - if (JenaPersistentDataSourceSetup.ds != null) { - JenaPersistentDataSourceSetup.ds.close(); - } - } - -} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/ModelMakerSetup.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/ModelMakerSetup.java deleted file mode 100644 index 6028408fe..000000000 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/ModelMakerSetup.java +++ /dev/null @@ -1,115 +0,0 @@ -/* $This file is distributed under the terms of the license in /doc/license.txt$ */ - -package edu.cornell.mannlib.vitro.webapp.servlet.setup; - -import static edu.cornell.mannlib.vitro.webapp.dao.ModelAccess.ModelID.APPLICATION_METADATA; -import static edu.cornell.mannlib.vitro.webapp.dao.ModelAccess.ModelID.BASE_FULL; -import static edu.cornell.mannlib.vitro.webapp.dao.ModelAccess.ModelID.BASE_TBOX; -import static edu.cornell.mannlib.vitro.webapp.dao.ModelAccess.ModelID.DISPLAY; -import static edu.cornell.mannlib.vitro.webapp.dao.ModelAccess.ModelID.DISPLAY_DISPLAY; -import static edu.cornell.mannlib.vitro.webapp.dao.ModelAccess.ModelID.DISPLAY_TBOX; -import static edu.cornell.mannlib.vitro.webapp.dao.ModelAccess.ModelID.INFERRED_FULL; -import static edu.cornell.mannlib.vitro.webapp.dao.ModelAccess.ModelID.INFERRED_TBOX; -import static edu.cornell.mannlib.vitro.webapp.dao.ModelAccess.ModelID.UNION_FULL; -import static edu.cornell.mannlib.vitro.webapp.dao.ModelAccess.ModelID.USER_ACCOUNTS; -import static edu.cornell.mannlib.vitro.webapp.dao.ModelAccess.ModelMakerID.CONFIGURATION; -import static edu.cornell.mannlib.vitro.webapp.dao.ModelAccess.ModelMakerID.CONTENT; - -import java.util.HashMap; -import java.util.Map; - -import javax.servlet.ServletContext; -import javax.servlet.ServletContextEvent; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import com.hp.hpl.jena.rdf.model.Model; - -import edu.cornell.mannlib.vitro.webapp.dao.ModelAccess; -import edu.cornell.mannlib.vitro.webapp.dao.jena.RDFServiceModelMaker; -import edu.cornell.mannlib.vitro.webapp.dao.jena.VitroInterceptingModelMaker; -import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelNames; -import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFServiceFactory; -import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.RDFServiceUtils; -import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.RDFServiceUtils.WhichService; -import edu.cornell.mannlib.vitro.webapp.startup.StartupStatus; - -/** - * Sets up the content models, OntModelSelectors and webapp DAO factories. - */ -public class ModelMakerSetup implements javax.servlet.ServletContextListener { - private static final Log log = LogFactory.getLog(ModelMakerSetup.class); - - @Override - public void contextInitialized(ServletContextEvent sce) { - ServletContext ctx = sce.getServletContext(); - StartupStatus ss = StartupStatus.getBean(ctx); - - createConfigurationModelMaker(ctx); - createContentModelMaker(ctx); - - ss.info(this, "Created model makers."); - } - - private void createConfigurationModelMaker(ServletContext ctx) { - RDFServiceFactory rdfServiceFactory = RDFServiceUtils - .getRDFServiceFactory(ctx, WhichService.CONFIGURATION); - RDFServiceModelMaker configMM = new RDFServiceModelMaker( - rdfServiceFactory); - Map specials = populateConfigurationSpecialMap(ctx); - VitroInterceptingModelMaker viMM = new VitroInterceptingModelMaker( - configMM, specials); - ModelAccess.on(ctx).setModelMaker(CONFIGURATION, viMM); - } - - private void createContentModelMaker(ServletContext ctx) { - RDFServiceFactory rdfServiceFactory = RDFServiceUtils - .getRDFServiceFactory(ctx); - RDFServiceModelMaker contentMM = new RDFServiceModelMaker( - rdfServiceFactory); - Map specials = populateContentSpecialMap(ctx); - VitroInterceptingModelMaker viMM = new VitroInterceptingModelMaker( - contentMM, specials); - ModelAccess.on(ctx).setModelMaker(CONTENT, viMM); - } - - private Map populateConfigurationSpecialMap( - ServletContext ctx) { - Map map = new HashMap<>(); - map.put(ModelNames.DISPLAY, - ModelAccess.on(ctx).getOntModel(DISPLAY)); - map.put(ModelNames.DISPLAY_TBOX, - ModelAccess.on(ctx).getOntModel(DISPLAY_TBOX)); - map.put(ModelNames.DISPLAY_DISPLAY, - ModelAccess.on(ctx).getOntModel(DISPLAY_DISPLAY)); - map.put(ModelNames.USER_ACCOUNTS, - ModelAccess.on(ctx).getOntModel(USER_ACCOUNTS)); - return map; - } - - private Map populateContentSpecialMap(ServletContext ctx) { - Map map = new HashMap<>(); - - map.put("vitro:jenaOntModel", - ModelAccess.on(ctx).getOntModel(UNION_FULL)); - map.put("vitro:baseOntModel", ModelAccess.on(ctx) - .getOntModel(BASE_FULL)); - map.put("vitro:inferenceOntModel", - ModelAccess.on(ctx).getOntModel(INFERRED_FULL)); - map.put(ModelNames.TBOX_ASSERTIONS, - ModelAccess.on(ctx).getOntModel(BASE_TBOX)); - map.put(ModelNames.TBOX_INFERENCES, - ModelAccess.on(ctx).getOntModel(INFERRED_TBOX)); - map.put(ModelNames.APPLICATION_METADATA, ModelAccess.on(ctx) - .getOntModel(APPLICATION_METADATA)); - - return map; - } - - @Override - public void contextDestroyed(ServletContextEvent sce) { - // Nothing to do. - } - -} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/RDFServiceSetup.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/RDFServiceSetup.java deleted file mode 100644 index a917be3f7..000000000 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/RDFServiceSetup.java +++ /dev/null @@ -1,176 +0,0 @@ -/* $This file is distributed under the terms of the license in /doc/license.txt$ */ -package edu.cornell.mannlib.vitro.webapp.servlet.setup; - -import static edu.cornell.mannlib.vitro.webapp.rdfservice.impl.RDFServiceUtils.WhichService.CONFIGURATION; - -import java.io.File; -import java.io.IOException; -import java.sql.SQLException; - -import javax.servlet.ServletContext; -import javax.servlet.ServletContextEvent; -import javax.sql.DataSource; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import com.hp.hpl.jena.sdb.SDB; -import com.hp.hpl.jena.sdb.SDBFactory; -import com.hp.hpl.jena.sdb.Store; -import com.hp.hpl.jena.sdb.StoreDesc; -import com.hp.hpl.jena.sdb.sql.SDBConnection; -import com.hp.hpl.jena.sdb.store.DatabaseType; -import com.hp.hpl.jena.sdb.store.LayoutType; -import com.hp.hpl.jena.sdb.util.StoreUtils; - -import edu.cornell.mannlib.vitro.webapp.config.ConfigurationProperties; -import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelNames; -import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService; -import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFServiceFactory; -import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.RDFServiceFactorySingle; -import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.RDFServiceUtils; -import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.jena.sdb.RDFServiceFactorySDB; -import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.jena.tdb.RDFServiceFactoryTDB; -import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.sparql.RDFServiceSparql; -import edu.cornell.mannlib.vitro.webapp.startup.StartupStatus; - -public class RDFServiceSetup extends JenaDataSourceSetupBase -implements javax.servlet.ServletContextListener { - private static final Log log = LogFactory.getLog(RDFServiceSetup.class); - private static final String DIRECTORY_TDB = "tdbModels"; - - @Override - public void contextDestroyed(ServletContextEvent arg0) { - // nothing to do - } - - @Override - public void contextInitialized(ServletContextEvent sce) { - ServletContext ctx = sce.getServletContext(); - StartupStatus ss = StartupStatus.getBean(ctx); - try { - String endpointURI = ConfigurationProperties.getBean(sce).getProperty( - "VitroConnection.DataSource.endpointURI"); - String updateEndpointURI = ConfigurationProperties.getBean(sce).getProperty( - "VitroConnection.DataSource.updateEndpointURI"); - if (endpointURI != null) { - useEndpoint(endpointURI, updateEndpointURI, ctx); - } else { - useSDB(ctx, ss); - } - - //experimental - //RDFServiceFactory factory = RDFServiceUtils.getRDFServiceFactory(ctx); - //RDFServiceUtils.setRDFServiceFactory(ctx, new SameAsFilteringRDFServiceFactory(factory)); - - useTDBForConfigurationModels(ctx); - - } catch (Exception e) { - ss.fatal(this, "Exception in RDFServiceSetup", e); - } - } - - private void useTDBForConfigurationModels(ServletContext ctx) throws IOException { - String vitroHome = ConfigurationProperties.getBean(ctx).getProperty( - "vitro.home") ; - String directoryPath = vitroHome + File.separatorChar + DIRECTORY_TDB; - RDFServiceFactory factory = new RDFServiceFactoryTDB(directoryPath); - RDFServiceUtils.setRDFServiceFactory(ctx, factory, CONFIGURATION); - } - - private void useEndpoint(String endpointURI, String updateEndpointURI, ServletContext ctx) { - - RDFService rdfService = null; - if (updateEndpointURI == null) { - rdfService = new RDFServiceSparql(endpointURI); - } else { - rdfService = new RDFServiceSparql(endpointURI, updateEndpointURI); - } - - RDFServiceFactory rdfServiceFactory = new RDFServiceFactorySingle(rdfService); - RDFServiceUtils.setRDFServiceFactory(ctx, rdfServiceFactory); - - if (updateEndpointURI != null) { - log.info("Using read endpoint at " + endpointURI); - log.info("Using update endpoint at " + updateEndpointURI); - } else { - log.info("Using endpoint at " + endpointURI); - } - } - - private void useSDB(ServletContext ctx, StartupStatus ss) throws SQLException { - DataSource ds = JenaPersistentDataSourceSetup.getApplicationDataSource(); - if( ds == null ){ - ss.fatal(this, "A DataSource must be setup before SDBSetup "+ - "is run. Make sure that JenaPersistentDataSourceSetup runs before "+ - "SDBSetup."); - return; - } - - // union default graph - SDB.getContext().set(SDB.unionDefaultGraph, true) ; - - StoreDesc storeDesc = makeStoreDesc(ctx); - Store store = connectStore(ds, storeDesc); - - if (!isSetUp(store)) { - JenaDataSourceSetupBase.thisIsFirstStartup(); - setupSDB(ctx, store); - } - - //RDFService rdfService = new RDFServiceSDB(ds, storeDesc); - //RDFServiceFactory rdfServiceFactory = new RDFServiceFactorySingle(rdfService); - - RDFServiceFactory rdfServiceFactory = new RDFServiceFactorySDB(ds, storeDesc); - RDFServiceUtils.setRDFServiceFactory(ctx, rdfServiceFactory); - - log.info("SDB store ready for use"); - - } - - - /** - * Tests whether an SDB store has been formatted and populated for use. - * @param store - * @return - */ - private boolean isSetUp(Store store) throws SQLException { - if (!(StoreUtils.isFormatted(store))) { - return false; - } - - // even if the store exists, it may be empty - - try { - return (SDBFactory.connectNamedModel( - store, - ModelNames.TBOX_ASSERTIONS)) - .size() > 0; - } catch (Exception e) { - return false; - } - } - - public static StoreDesc makeStoreDesc(ServletContext ctx) { - String layoutStr = ConfigurationProperties.getBean(ctx).getProperty( - "VitroConnection.DataSource.sdb.layout", "layout2/hash"); - String dbtypeStr = ConfigurationProperties.getBean(ctx).getProperty( - "VitroConnection.DataSource.dbtype", "MySQL"); - return new StoreDesc( - LayoutType.fetch(layoutStr), - DatabaseType.fetch(dbtypeStr) ); - } - - public static Store connectStore(DataSource bds, StoreDesc storeDesc) - throws SQLException { - SDBConnection conn = new SDBConnection(bds.getConnection()); - return SDBFactory.connectStore(conn, storeDesc); - } - - protected static void setupSDB(ServletContext ctx, Store store) { - log.info("Initializing SDB store"); - store.getTableFormatter().create(); - store.getTableFormatter().truncate(); - } - -} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/rdfsetup/RDFSetup.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/rdfsetup/RDFSetup.java new file mode 100644 index 000000000..97a07e7fe --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/rdfsetup/RDFSetup.java @@ -0,0 +1,94 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.servlet.setup.rdfsetup; + +import static edu.cornell.mannlib.vitro.webapp.servlet.setup.rdfsetup.impl.sparql.RDFSourceSPARQL.PROPERTY_SPARQL_ENDPOINT_URI; + +import javax.servlet.ServletContext; +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; + +import org.apache.commons.lang.StringUtils; + +import com.hp.hpl.jena.ontology.OntDocumentManager; + +import edu.cornell.mannlib.vitro.webapp.config.ConfigurationProperties; +import edu.cornell.mannlib.vitro.webapp.dao.ModelAccess; +import edu.cornell.mannlib.vitro.webapp.dao.ModelAccess.ModelMakerID; +import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelMakerUtils; +import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.RDFServiceUtils; +import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.RDFServiceUtils.WhichService; +import edu.cornell.mannlib.vitro.webapp.servlet.setup.rdfsetup.impl.sdb.RDFSourceSDB; +import edu.cornell.mannlib.vitro.webapp.servlet.setup.rdfsetup.impl.sparql.RDFSourceSPARQL; +import edu.cornell.mannlib.vitro.webapp.servlet.setup.rdfsetup.impl.tdb.RDFSourceTDB; + +/** + * Create the RDFServiceFactories and ModelMakers for the application to use. + */ +public class RDFSetup implements ServletContextListener { + private ServletContext ctx; + private ConfigurationProperties configProps; + + private RDFSource contentRdfSource; + private RDFSource configurationRdfSource; + + @Override + public void contextInitialized(ServletContextEvent sce) { + this.ctx = sce.getServletContext(); + this.configProps = ConfigurationProperties.getBean(ctx); + + configureJena(); + + createRdfSources(); + + RDFServiceUtils.setRDFServiceFactory(ctx, + contentRdfSource.getRDFServiceFactory(), WhichService.CONTENT); + ModelMakerUtils.setContentModelMakerFactory(ctx, + contentRdfSource.getContentModelMakerFactory()); + ModelAccess.on(ctx).setModelMaker(ModelMakerID.CONTENT, + ModelMakerUtils.getModelMaker(ctx, WhichService.CONTENT)); + + RDFServiceUtils.setRDFServiceFactory(ctx, + configurationRdfSource.getRDFServiceFactory(), + WhichService.CONFIGURATION); + ModelMakerUtils.setConfigurationModelMakerFactory(ctx, + configurationRdfSource.getConfigurationModelMakerFactory()); + ModelAccess.on(ctx).setModelMaker(ModelMakerID.CONFIGURATION, + ModelMakerUtils.getModelMaker(ctx, WhichService.CONFIGURATION)); + + } + + private void configureJena() { + // we do not want to fetch imports when we wrap Models in OntModels + OntDocumentManager.getInstance().setProcessImports(false); + } + + /** + * For now, these steps are hard-coded. They should be driven by a + * configuration file. + */ + private void createRdfSources() { + if (isSparqlEndpointContentConfigured()) { + contentRdfSource = new RDFSourceSPARQL(ctx, this); + } else { + contentRdfSource = new RDFSourceSDB(ctx, this); + } + configurationRdfSource = new RDFSourceTDB(ctx, this); + } + + private boolean isSparqlEndpointContentConfigured() { + return StringUtils.isNotBlank(configProps + .getProperty(PROPERTY_SPARQL_ENDPOINT_URI)); + } + + @Override + public void contextDestroyed(ServletContextEvent sce) { + if (configurationRdfSource != null) { + configurationRdfSource.close(); + } + if (contentRdfSource != null) { + contentRdfSource.close(); + } + } + +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/rdfsetup/RDFSource.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/rdfsetup/RDFSource.java new file mode 100644 index 000000000..9528a3bad --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/rdfsetup/RDFSource.java @@ -0,0 +1,25 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.servlet.setup.rdfsetup; + +import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelMakerFactory; +import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFServiceFactory; + +/** + * The interface for a triple-store implementation. It returns an + * RDFServiceFactory and either or both of the ModelMakerFactories. + * + * You should call close() when shutting down the triple-store. + */ +public interface RDFSource extends AutoCloseable { + + RDFServiceFactory getRDFServiceFactory(); + + ModelMakerFactory getContentModelMakerFactory(); + + ModelMakerFactory getConfigurationModelMakerFactory(); + + @Override + void close(); + +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/rdfsetup/impl/sdb/ConfigurationModelMakerFactorySDB.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/rdfsetup/impl/sdb/ConfigurationModelMakerFactorySDB.java new file mode 100644 index 000000000..ec037d72a --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/rdfsetup/impl/sdb/ConfigurationModelMakerFactorySDB.java @@ -0,0 +1,45 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.servlet.setup.rdfsetup.impl.sdb; + +import com.hp.hpl.jena.rdf.model.ModelMaker; + +import edu.cornell.mannlib.vitro.webapp.dao.jena.RDFServiceModelMaker; +import edu.cornell.mannlib.vitro.webapp.modelaccess.ConfigurationModelMakerFactory; +import edu.cornell.mannlib.vitro.webapp.modelaccess.adapters.ListCachingModelMaker; +import edu.cornell.mannlib.vitro.webapp.modelaccess.adapters.MemoryMappingModelMaker; +import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService; + +/** + * In SDB, Configuration models require database connections. + * + * However, they are all small enough for memory-mapping. Once memory-mapped, + * they are suitable for short-term or long-term use. + * + * RDFService doesn't support empty models, so support them with ListCaching + */ +public class ConfigurationModelMakerFactorySDB extends + ConfigurationModelMakerFactory { + + private final ModelMaker longTermModelMaker; + + public ConfigurationModelMakerFactorySDB(RDFService longTermRdfService) { + this.longTermModelMaker = new ListCachingModelMaker( + new MemoryMappingModelMaker(new RDFServiceModelMaker( + longTermRdfService), CONFIGURATION_MODELS)); + } + + @Override + public ModelMaker getModelMaker(RDFService longTermRdfService) { + return addConfigurationDecorators(longTermModelMaker); + } + + /** + * The long-term models are all memory-mapped, so use them. + */ + @Override + public ModelMaker getShortTermModelMaker(RDFService shortTermRdfService) { + return addConfigurationDecorators(longTermModelMaker); + } + +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/rdfsetup/impl/sdb/ContentModelMakerFactorySDB.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/rdfsetup/impl/sdb/ContentModelMakerFactorySDB.java new file mode 100644 index 000000000..08b51ae22 --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/rdfsetup/impl/sdb/ContentModelMakerFactorySDB.java @@ -0,0 +1,59 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.servlet.setup.rdfsetup.impl.sdb; + +import com.hp.hpl.jena.rdf.model.ModelMaker; + +import edu.cornell.mannlib.vitro.webapp.dao.jena.RDFServiceModelMaker; +import edu.cornell.mannlib.vitro.webapp.modelaccess.ContentModelMakerFactory; +import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelMakerFactory; +import edu.cornell.mannlib.vitro.webapp.modelaccess.adapters.ListCachingModelMaker; +import edu.cornell.mannlib.vitro.webapp.modelaccess.adapters.MemoryMappingModelMaker; +import edu.cornell.mannlib.vitro.webapp.modelaccess.adapters.ShadowingModelMaker; +import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService; + +/** + * In SDB, Content models require database connections. + * + * Memory-map the small models, and use a short-term connection for the others + * (when available). + * + * RDFService doesn't support empty models, so support them with ListCaching + */ +public class ContentModelMakerFactorySDB extends ContentModelMakerFactory + implements ModelMakerFactory { + + private final ModelMaker longTermModelMaker; + + public ContentModelMakerFactorySDB(RDFService longTermRdfService) { + this.longTermModelMaker = new ListCachingModelMaker( + new MemoryMappingModelMaker(new RDFServiceModelMaker( + longTermRdfService), SMALL_CONTENT_MODELS)); + } + + /** + * The small content models (tbox, app_metadata) are memory mapped, for + * speed. + */ + @Override + public ModelMaker getModelMaker(RDFService longTermRdfService) { + return addContentDecorators(longTermModelMaker); + } + + /** + * For short-term use, the large models (abox) will come from a short-term + * service. The small models can be the memory-mapped ones that we created + * for long-term use. + */ + @Override + public ModelMaker getShortTermModelMaker(RDFService shortTermRdfService) { + ModelMaker shortTermModelMaker = new RDFServiceModelMaker( + shortTermRdfService); + + // No need to create a fresh memory map of the small models: use the + // long-term ones. + return addContentDecorators(new ShadowingModelMaker( + shortTermModelMaker, longTermModelMaker, SMALL_CONTENT_MODELS)); + } + +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/rdfsetup/impl/sdb/RDFSourceSDB.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/rdfsetup/impl/sdb/RDFSourceSDB.java new file mode 100644 index 000000000..190e20289 --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/rdfsetup/impl/sdb/RDFSourceSDB.java @@ -0,0 +1,173 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.servlet.setup.rdfsetup.impl.sdb; + +import java.sql.SQLException; + +import javax.servlet.ServletContext; +import javax.servlet.ServletContextListener; +import javax.sql.DataSource; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import com.hp.hpl.jena.sdb.SDB; +import com.hp.hpl.jena.sdb.SDBFactory; +import com.hp.hpl.jena.sdb.Store; +import com.hp.hpl.jena.sdb.StoreDesc; +import com.hp.hpl.jena.sdb.sql.SDBConnection; +import com.hp.hpl.jena.sdb.store.DatabaseType; +import com.hp.hpl.jena.sdb.store.LayoutType; +import com.hp.hpl.jena.sdb.util.StoreUtils; +import com.mchange.v2.c3p0.ComboPooledDataSource; + +import edu.cornell.mannlib.vitro.webapp.config.ConfigurationProperties; +import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelMakerFactory; +import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelNames; +import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService; +import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFServiceFactory; +import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.jena.sdb.RDFServiceFactorySDB; +import edu.cornell.mannlib.vitro.webapp.servlet.setup.JenaDataSourceSetupBase; +import edu.cornell.mannlib.vitro.webapp.servlet.setup.rdfsetup.RDFSource; +import edu.cornell.mannlib.vitro.webapp.startup.StartupStatus; + +/** + * Create the connection to the SDB triple-store. + * + * Do some smoke-tests on the parameters, create the connection pool, and create + * the RDFServiceFactory. + * + * Create the ModelMakerFactories only if requested. + */ +public class RDFSourceSDB implements RDFSource { + private static final Log log = LogFactory.getLog(RDFSourceSDB.class); + + static final String PROPERTY_DB_URL = "VitroConnection.DataSource.url"; + static final String PROPERTY_DB_USERNAME = "VitroConnection.DataSource.username"; + static final String PROPERTY_DB_PASSWORD = "VitroConnection.DataSource.password"; + static final String PROPERTY_DB_DRIVER_CLASS_NAME = "VitroConnection.DataSource.driver"; + static final String PROPERTY_DB_TYPE = "VitroConnection.DataSource.dbtype"; + static final String PROPERTY_DB_MAX_ACTIVE = "VitroConnection.DataSource.pool.maxActive"; + static final String PROPERTY_DB_MAX_IDLE = "VitroConnection.DataSource.pool.maxIdle"; + static final String PROPERTY_DB_VALIDATION_QUERY = "VitroConnection.DataSource.validationQuery"; + static final String PROPERTY_DB_SDB_LAYOUT = "VitroConnection.DataSource.sdb.layout"; + + static final String DEFAULT_TYPE = "MySQL"; + static final String DEFAULT_DRIVER_CLASS = "com.mysql.jdbc.Driver"; + static final String DEFAULT_LAYOUT = "layout2/hash"; + static final String DEFAULT_VALIDATION_QUERY = "SELECT 1"; + + static final int DEFAULT_MAXACTIVE = 40; // ms + static final int MINIMUM_MAXACTIVE = 20; // ms + static final int DEFAULT_MAXIDLE = 10; // ms + + static final boolean DEFAULT_TESTONBORROW = true; + static final boolean DEFAULT_TESTONRETURN = true; + + private final ServletContext ctx; + private final StartupStatus ss; + private final ComboPooledDataSource ds; + private final RDFServiceFactory rdfServiceFactory; + private final RDFService rdfService; + + public RDFSourceSDB(ServletContext ctx, ServletContextListener parent) { + try { + this.ctx = ctx; + this.ss = StartupStatus.getBean(ctx); + + configureSDBContext(); + + new SDBConnectionSmokeTests(ctx, parent) + .checkDatabaseConnection(); + + this.ds = new SDBDataSource(ctx).getDataSource(); + this.rdfServiceFactory = createRdfServiceFactory(); + this.rdfService = rdfServiceFactory.getRDFService(); + ss.info(parent, "Initialized the RDF source for SDB"); + } catch (SQLException e) { + throw new RuntimeException( + "Failed to set up the RDF source for SDB", e); + } + } + + private void configureSDBContext() { + SDB.getContext().set(SDB.unionDefaultGraph, true); + } + + private RDFServiceFactory createRdfServiceFactory() throws SQLException { + StoreDesc storeDesc = makeStoreDesc(); + Store store = connectStore(ds, storeDesc); + + if (!isSetUp(store)) { + JenaDataSourceSetupBase.thisIsFirstStartup(); + setupSDB(store); + } + + return new RDFServiceFactorySDB(ds, storeDesc); + } + + /** + * Tests whether an SDB store has been formatted and populated for use. + * + * @param store + * @return + */ + private boolean isSetUp(Store store) throws SQLException { + if (!(StoreUtils.isFormatted(store))) { + return false; + } + + // even if the store exists, it may be empty + + try { + return (SDBFactory.connectNamedModel(store, + ModelNames.TBOX_ASSERTIONS)).size() > 0; + } catch (Exception e) { + return false; + } + } + + private StoreDesc makeStoreDesc() { + ConfigurationProperties props = ConfigurationProperties.getBean(ctx); + String layoutStr = props.getProperty(PROPERTY_DB_SDB_LAYOUT, + DEFAULT_LAYOUT); + String dbtypeStr = props.getProperty(PROPERTY_DB_TYPE, DEFAULT_TYPE); + return new StoreDesc(LayoutType.fetch(layoutStr), + DatabaseType.fetch(dbtypeStr)); + } + + private Store connectStore(DataSource bds, StoreDesc storeDesc) + throws SQLException { + SDBConnection conn = new SDBConnection(bds.getConnection()); + return SDBFactory.connectStore(conn, storeDesc); + } + + private void setupSDB(Store store) { + log.info("Initializing SDB store"); + store.getTableFormatter().create(); + store.getTableFormatter().truncate(); + } + + @Override + public RDFServiceFactory getRDFServiceFactory() { + return this.rdfServiceFactory; + } + + @Override + public ModelMakerFactory getContentModelMakerFactory() { + return new ContentModelMakerFactorySDB(this.rdfService); + } + + @Override + public ModelMakerFactory getConfigurationModelMakerFactory() { + return new ConfigurationModelMakerFactorySDB(this.rdfService); + } + + @Override + public void close() { + if (ds != null) { + ds.close(); + } + } + +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/rdfsetup/impl/sdb/SDBConnectionSmokeTests.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/rdfsetup/impl/sdb/SDBConnectionSmokeTests.java new file mode 100644 index 000000000..ad9162c0c --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/rdfsetup/impl/sdb/SDBConnectionSmokeTests.java @@ -0,0 +1,177 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.servlet.setup.rdfsetup.impl.sdb; + +import static edu.cornell.mannlib.vitro.webapp.servlet.setup.rdfsetup.impl.sdb.RDFSourceSDB.DEFAULT_DRIVER_CLASS; +import static edu.cornell.mannlib.vitro.webapp.servlet.setup.rdfsetup.impl.sdb.RDFSourceSDB.PROPERTY_DB_DRIVER_CLASS_NAME; +import static edu.cornell.mannlib.vitro.webapp.servlet.setup.rdfsetup.impl.sdb.RDFSourceSDB.PROPERTY_DB_PASSWORD; +import static edu.cornell.mannlib.vitro.webapp.servlet.setup.rdfsetup.impl.sdb.RDFSourceSDB.PROPERTY_DB_TYPE; +import static edu.cornell.mannlib.vitro.webapp.servlet.setup.rdfsetup.impl.sdb.RDFSourceSDB.PROPERTY_DB_URL; +import static edu.cornell.mannlib.vitro.webapp.servlet.setup.rdfsetup.impl.sdb.RDFSourceSDB.PROPERTY_DB_USERNAME; + +import java.io.UnsupportedEncodingException; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Properties; + +import javax.servlet.ServletContext; +import javax.servlet.ServletContextListener; + +import edu.cornell.mannlib.vitro.webapp.config.ConfigurationProperties; +import edu.cornell.mannlib.vitro.webapp.startup.StartupStatus; + +/** + * Smoke tests for the database connection that SDB will use. + * + * Confirm that the URL, Username and Password have been specified for the + * Database connection. + * + * Confirm that we can load the database driver. + * + * Confirm that we can connect to the database using those properties. + * + * Try to confirm that the database connection is configured to use UTF-8 + * encoding. Don't know how well this works. + */ +public class SDBConnectionSmokeTests { + private final ServletContextListener parent; + private final ConfigurationProperties props; + private final StartupStatus ss; + + public SDBConnectionSmokeTests(ServletContext ctx, + ServletContextListener parent) { + this.parent = parent; + this.props = ConfigurationProperties.getBean(ctx); + this.ss = StartupStatus.getBean(ctx); + + } + + public void checkDatabaseConnection() { + String url = props.getProperty(PROPERTY_DB_URL); + if (url == null || url.isEmpty()) { + ss.fatal(parent, + "runtime.properties does not contain a value for '" + + PROPERTY_DB_URL + "'"); + return; + } + String username = props.getProperty(PROPERTY_DB_USERNAME); + if (username == null || username.isEmpty()) { + ss.fatal(parent, + "runtime.properties does not contain a value for '" + + PROPERTY_DB_USERNAME + "'"); + return; + } + String password = props.getProperty(PROPERTY_DB_PASSWORD); + if (password == null || password.isEmpty()) { + ss.fatal(parent, + "runtime.properties does not contain a value for '" + + PROPERTY_DB_PASSWORD + "'"); + return; + } + + String driverClassName = props + .getProperty(PROPERTY_DB_DRIVER_CLASS_NAME); + if (driverClassName == null) { + try { + Class.forName(DEFAULT_DRIVER_CLASS).newInstance(); + } catch (Exception e) { + ss.fatal(parent, "The default Database Driver failed to load. " + + "The driver class is '" + DEFAULT_DRIVER_CLASS + + "'", e); + return; + } + } else { + try { + Class.forName(driverClassName).newInstance(); + } catch (Exception e) { + ss.fatal(parent, "The Database Driver failed to load. " + + "The driver class was set by " + + PROPERTY_DB_DRIVER_CLASS_NAME + " to be '" + + driverClassName + "'", e); + return; + } + } + + Properties connectionProps = new Properties(); + connectionProps.put("user", username); + connectionProps.put("password", password); + + try (Connection conn = DriverManager + .getConnection(url, connectionProps)) { + // Just open the connection and close it. + } catch (SQLException e) { + ss.fatal(parent, "Can't connect to the database: " + + PROPERTY_DB_URL + "='" + url + "', " + + PROPERTY_DB_USERNAME + "='" + username + "'", e); + return; + } + + String dbType = props.getProperty(PROPERTY_DB_TYPE, "MySQL"); + checkForPropertHandlingOfUnicodeCharacters(url, connectionProps, dbType); + } + + private void checkForPropertHandlingOfUnicodeCharacters(String url, + Properties connectionProps, String dbType) { + String testString = "ABC\u00CE\u0123"; + + try (Connection conn = DriverManager + .getConnection(url, connectionProps); + Statement stmt = conn.createStatement()) { + + // Create the temporary table. + stmt.executeUpdate("CREATE TEMPORARY TABLE smoke_test (contents varchar(100))"); + + // Write the test string, encoding in UTF-8 on the way in. + try (PreparedStatement pstmt = conn + .prepareStatement("INSERT INTO smoke_test values ( ? )")) { + pstmt.setBytes(1, testString.getBytes("UTF-8")); + pstmt.executeUpdate(); + } catch (UnsupportedEncodingException e1) { + e1.printStackTrace(); + } + + // Read it back as a String. Does the database decode it properly? + ResultSet rs = stmt.executeQuery("SELECT * FROM smoke_test"); + if (!rs.next()) { + throw new SQLException( + "Query of temporary table returned 0 rows."); + } + String storedValue = rs.getString(1); + if (!testString.equals(storedValue)) { + String message = "The database does not store Unicode " + + "characters correctly. The test inserted \"" + + showUnicode(testString) + + "\", but the query returned \"" + + showUnicode(storedValue) + + "\". Is the character encoding correctly " + + "set on the database?"; + if ("MySQL".equals(dbType)) { + // For MySQL, we know that this is a configuration problem. + ss.fatal(parent, message); + } else { + // For other databases, it might not be. + ss.warning(parent, message); + } + } + } catch (SQLException e) { + ss.fatal(parent, "Failed to check handling of Unicode characters", + e); + } + } + + /** + * Display the hex codes for a String. + */ + private String showUnicode(String testString) { + StringBuilder u = new StringBuilder(); + for (char c : testString.toCharArray()) { + u.append(String.format("\\u%04x", c & 0x0000FFFF)); + } + return u.toString(); + } + +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/rdfsetup/impl/sdb/SDBDataSource.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/rdfsetup/impl/sdb/SDBDataSource.java new file mode 100644 index 000000000..d598bc185 --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/rdfsetup/impl/sdb/SDBDataSource.java @@ -0,0 +1,136 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.servlet.setup.rdfsetup.impl.sdb; + +import static edu.cornell.mannlib.vitro.webapp.servlet.setup.rdfsetup.impl.sdb.RDFSourceSDB.DEFAULT_DRIVER_CLASS; +import static edu.cornell.mannlib.vitro.webapp.servlet.setup.rdfsetup.impl.sdb.RDFSourceSDB.DEFAULT_MAXACTIVE; +import static edu.cornell.mannlib.vitro.webapp.servlet.setup.rdfsetup.impl.sdb.RDFSourceSDB.DEFAULT_MAXIDLE; +import static edu.cornell.mannlib.vitro.webapp.servlet.setup.rdfsetup.impl.sdb.RDFSourceSDB.DEFAULT_TESTONBORROW; +import static edu.cornell.mannlib.vitro.webapp.servlet.setup.rdfsetup.impl.sdb.RDFSourceSDB.DEFAULT_TESTONRETURN; +import static edu.cornell.mannlib.vitro.webapp.servlet.setup.rdfsetup.impl.sdb.RDFSourceSDB.DEFAULT_TYPE; +import static edu.cornell.mannlib.vitro.webapp.servlet.setup.rdfsetup.impl.sdb.RDFSourceSDB.DEFAULT_VALIDATION_QUERY; +import static edu.cornell.mannlib.vitro.webapp.servlet.setup.rdfsetup.impl.sdb.RDFSourceSDB.MINIMUM_MAXACTIVE; +import static edu.cornell.mannlib.vitro.webapp.servlet.setup.rdfsetup.impl.sdb.RDFSourceSDB.PROPERTY_DB_DRIVER_CLASS_NAME; +import static edu.cornell.mannlib.vitro.webapp.servlet.setup.rdfsetup.impl.sdb.RDFSourceSDB.PROPERTY_DB_MAX_ACTIVE; +import static edu.cornell.mannlib.vitro.webapp.servlet.setup.rdfsetup.impl.sdb.RDFSourceSDB.PROPERTY_DB_MAX_IDLE; +import static edu.cornell.mannlib.vitro.webapp.servlet.setup.rdfsetup.impl.sdb.RDFSourceSDB.PROPERTY_DB_PASSWORD; +import static edu.cornell.mannlib.vitro.webapp.servlet.setup.rdfsetup.impl.sdb.RDFSourceSDB.PROPERTY_DB_TYPE; +import static edu.cornell.mannlib.vitro.webapp.servlet.setup.rdfsetup.impl.sdb.RDFSourceSDB.PROPERTY_DB_URL; +import static edu.cornell.mannlib.vitro.webapp.servlet.setup.rdfsetup.impl.sdb.RDFSourceSDB.PROPERTY_DB_USERNAME; +import static edu.cornell.mannlib.vitro.webapp.servlet.setup.rdfsetup.impl.sdb.RDFSourceSDB.PROPERTY_DB_VALIDATION_QUERY; + +import java.beans.PropertyVetoException; + +import javax.servlet.ServletContext; + +import org.apache.commons.lang.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import com.mchange.v2.c3p0.ComboPooledDataSource; + +import edu.cornell.mannlib.vitro.webapp.config.ConfigurationProperties; + +/** + * Create a DataSource on the SDB database. + */ +public class SDBDataSource { + private static final Log log = LogFactory.getLog(SDBDataSource.class); + + private final ConfigurationProperties configProps; + + + public SDBDataSource(ServletContext ctx) { + this.configProps = ConfigurationProperties.getBean(ctx); + } + + public ComboPooledDataSource getDataSource() { + try { + ComboPooledDataSource cpds = new ComboPooledDataSource(); + cpds.setDriverClass(getDbDriverClassName()); + cpds.setJdbcUrl(getJdbcUrl()); + cpds.setUser(configProps.getProperty(PROPERTY_DB_USERNAME)); + cpds.setPassword(configProps.getProperty(PROPERTY_DB_PASSWORD)); + cpds.setMaxPoolSize(getMaxActive()); + cpds.setMinPoolSize(getMaxIdle()); + cpds.setMaxIdleTime(43200); // s + cpds.setMaxIdleTimeExcessConnections(300); + cpds.setAcquireIncrement(5); + cpds.setNumHelperThreads(6); + cpds.setTestConnectionOnCheckout(DEFAULT_TESTONBORROW); + cpds.setTestConnectionOnCheckin(DEFAULT_TESTONRETURN); + cpds.setPreferredTestQuery(getValidationQuery()); + return cpds; + } catch (PropertyVetoException pve) { + throw new RuntimeException(pve); + } + } + + private String getDbDriverClassName() { + return configProps.getProperty(PROPERTY_DB_DRIVER_CLASS_NAME, + DEFAULT_DRIVER_CLASS); + } + + private String getDbType() { + return configProps.getProperty(PROPERTY_DB_TYPE, DEFAULT_TYPE); + } + + private String getJdbcUrl() { + String url = configProps.getProperty(PROPERTY_DB_URL); + + // Ensure that MySQL handles unicode properly, else all kinds of + // horrible nastiness ensues. + if (DEFAULT_TYPE.equals(getDbType()) && !url.contains("?")) { + url += "?useUnicode=yes&characterEncoding=utf8"; + } + + return url; + } + + private String getValidationQuery() { + return configProps.getProperty(PROPERTY_DB_VALIDATION_QUERY, + DEFAULT_VALIDATION_QUERY); + } + + private int getMaxActive() { + String maxActiveStr = configProps.getProperty(PROPERTY_DB_MAX_ACTIVE); + if (StringUtils.isEmpty(maxActiveStr)) { + return DEFAULT_MAXACTIVE; + } + + int maxActive = DEFAULT_MAXACTIVE; + try { + maxActive = Integer.parseInt(maxActiveStr); + } catch (NumberFormatException nfe) { + log.error("Unable to parse connection pool maxActive setting " + + maxActiveStr + " as an integer"); + return DEFAULT_MAXACTIVE; + } + + if (maxActive >= MINIMUM_MAXACTIVE) { + return maxActive; + } + + log.warn("Specified value for " + PROPERTY_DB_MAX_ACTIVE + + " is too low. Using minimum value of " + MINIMUM_MAXACTIVE); + return MINIMUM_MAXACTIVE; + } + + private int getMaxIdle() { + int maxIdleInt = Math.max(getMaxActive() / 4, DEFAULT_MAXIDLE); + String maxIdleStr = configProps.getProperty(PROPERTY_DB_MAX_IDLE); + + if (StringUtils.isEmpty(maxIdleStr)) { + return maxIdleInt; + } + + try { + return Integer.parseInt(maxIdleStr); + } catch (NumberFormatException nfe) { + log.error("Unable to parse connection pool maxIdle setting " + + maxIdleStr + " as an integer"); + return maxIdleInt; + } + } + +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/rdfsetup/impl/sparql/ConfigurationModelMakerFactorySPARQL.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/rdfsetup/impl/sparql/ConfigurationModelMakerFactorySPARQL.java new file mode 100644 index 000000000..c516d7e57 --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/rdfsetup/impl/sparql/ConfigurationModelMakerFactorySPARQL.java @@ -0,0 +1,46 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.servlet.setup.rdfsetup.impl.sparql; + +import com.hp.hpl.jena.rdf.model.ModelMaker; + +import edu.cornell.mannlib.vitro.webapp.dao.jena.RDFServiceModelMaker; +import edu.cornell.mannlib.vitro.webapp.modelaccess.ConfigurationModelMakerFactory; +import edu.cornell.mannlib.vitro.webapp.modelaccess.adapters.ListCachingModelMaker; +import edu.cornell.mannlib.vitro.webapp.modelaccess.adapters.MemoryMappingModelMaker; +import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService; + +/** + * On a SPARQL endpoint, is there any difference between short-term and + * long-term connections? + * + * In any case, memory-map all of the Configuration models, and apply them to + * both short-term and long-term use. + * + * RDFService doesn't support empty models, so support them with ListCaching + */ +public class ConfigurationModelMakerFactorySPARQL extends + ConfigurationModelMakerFactory { + + private final ModelMaker longTermModelMaker; + + public ConfigurationModelMakerFactorySPARQL(RDFService longTermRdfService) { + this.longTermModelMaker = new ListCachingModelMaker(new MemoryMappingModelMaker( + new RDFServiceModelMaker(longTermRdfService), + CONFIGURATION_MODELS)); + } + + @Override + public ModelMaker getModelMaker(RDFService longTermRdfService) { + return addConfigurationDecorators(longTermModelMaker); + } + + /** + * The long-term models are all memory-mapped, so use them. + */ + @Override + public ModelMaker getShortTermModelMaker(RDFService shortTermRdfService) { + return addConfigurationDecorators(longTermModelMaker); + } + +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/rdfsetup/impl/sparql/ContentModelMakerFactorySPARQL.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/rdfsetup/impl/sparql/ContentModelMakerFactorySPARQL.java new file mode 100644 index 000000000..f8d636ceb --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/rdfsetup/impl/sparql/ContentModelMakerFactorySPARQL.java @@ -0,0 +1,60 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.servlet.setup.rdfsetup.impl.sparql; + +import com.hp.hpl.jena.rdf.model.ModelMaker; + +import edu.cornell.mannlib.vitro.webapp.dao.jena.RDFServiceModelMaker; +import edu.cornell.mannlib.vitro.webapp.modelaccess.ContentModelMakerFactory; +import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelMakerFactory; +import edu.cornell.mannlib.vitro.webapp.modelaccess.adapters.ListCachingModelMaker; +import edu.cornell.mannlib.vitro.webapp.modelaccess.adapters.MemoryMappingModelMaker; +import edu.cornell.mannlib.vitro.webapp.modelaccess.adapters.ShadowingModelMaker; +import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService; + +/** + * On a SPARQL endpoint, is there any difference between short-term and + * long-term connections? + * + * Anyway, memory-map the small models, and use a short-term connection for the + * others (when available). + * + * RDFService doesn't support empty models, so support them with ListCaching + */ +public class ContentModelMakerFactorySPARQL extends ContentModelMakerFactory + implements ModelMakerFactory { + + private final ModelMaker longTermModelMaker; + + public ContentModelMakerFactorySPARQL(RDFService longTermRdfService) { + this.longTermModelMaker = new ListCachingModelMaker(new MemoryMappingModelMaker( + new RDFServiceModelMaker(longTermRdfService), + SMALL_CONTENT_MODELS)); + } + + /** + * The small content models (tbox, app_metadata) are memory mapped, for + * speed. + */ + @Override + public ModelMaker getModelMaker(RDFService longTermRdfService) { + return addContentDecorators(longTermModelMaker); + } + + /** + * For short-term use, the large models (abox) will come from a short-term + * service. The small models can be the memory-mapped ones that we created + * for long-term use. + */ + @Override + public ModelMaker getShortTermModelMaker(RDFService shortTermRdfService) { + ModelMaker shortTermModelMaker = new RDFServiceModelMaker( + shortTermRdfService); + + // No need to create a fresh memory map of the small models: use the + // long-term ones. + return addContentDecorators(new ShadowingModelMaker( + shortTermModelMaker, longTermModelMaker, SMALL_CONTENT_MODELS)); + } + +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/rdfsetup/impl/sparql/RDFSourceSPARQL.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/rdfsetup/impl/sparql/RDFSourceSPARQL.java new file mode 100644 index 000000000..58166d9dc --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/rdfsetup/impl/sparql/RDFSourceSPARQL.java @@ -0,0 +1,93 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.servlet.setup.rdfsetup.impl.sparql; + +import javax.servlet.ServletContext; +import javax.servlet.ServletContextListener; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import edu.cornell.mannlib.vitro.webapp.config.ConfigurationProperties; +import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelMakerFactory; +import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService; +import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFServiceFactory; +import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.RDFServiceFactorySingle; +import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.sparql.RDFServiceSparql; +import edu.cornell.mannlib.vitro.webapp.servlet.setup.rdfsetup.RDFSource; +import edu.cornell.mannlib.vitro.webapp.startup.StartupStatus; + +/** + * Create the connection to a SPARQL endpoint. + * + * Depending on the settings, we might use two endpoints: one for reads and one + * for updates. + * + * Create the RDFServiceFactory. + */ +public class RDFSourceSPARQL implements RDFSource { + private static final Log log = LogFactory.getLog(RDFSourceSPARQL.class); + + public static final String PROPERTY_SPARQL_ENDPOINT_URI = "VitroConnection.DataSource.endpointURI"; + public static final String PROPERTY_SPARQL_UPDATE_ENDPOINT_URI = "VitroConnection.DataSource.updateEndpointURI"; + + private final ServletContextListener parent; + private final ConfigurationProperties props; + private final StartupStatus ss; + private final String endpointURI; + private final String updateEndpointURI; + + private final RDFService rdfService; + private final RDFServiceFactory rdfServiceFactory; + + public RDFSourceSPARQL(ServletContext ctx, ServletContextListener parent) { + this.parent = parent; + this.props = ConfigurationProperties.getBean(ctx); + this.ss = StartupStatus.getBean(ctx); + + this.endpointURI = props.getProperty(PROPERTY_SPARQL_ENDPOINT_URI); + this.updateEndpointURI = props + .getProperty(PROPERTY_SPARQL_UPDATE_ENDPOINT_URI); + + this.rdfService = createRDFService(); + this.rdfServiceFactory = createRDFServiceFactory(); + } + + private RDFService createRDFService() { + if (updateEndpointURI == null) { + ss.info(parent, "Using endpoint at " + endpointURI); + return new RDFServiceSparql(endpointURI); + } else { + ss.info(parent, "Using read endpoint at " + endpointURI + + " and update endpoint at " + updateEndpointURI); + return new RDFServiceSparql(endpointURI, updateEndpointURI); + } + } + + private RDFServiceFactory createRDFServiceFactory() { + return new RDFServiceFactorySingle(this.rdfService); + } + + @Override + public RDFServiceFactory getRDFServiceFactory() { + return this.rdfServiceFactory; + } + + @Override + public ModelMakerFactory getContentModelMakerFactory() { + return new ContentModelMakerFactorySPARQL(this.rdfService); + } + + @Override + public ModelMakerFactory getConfigurationModelMakerFactory() { + return new ConfigurationModelMakerFactorySPARQL(this.rdfService); + } + + @Override + public void close() { + if (this.rdfService != null) { + this.rdfService.close(); + } + } + +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/rdfsetup/impl/tdb/ConfigurationModelMakerFactoryTDB.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/rdfsetup/impl/tdb/ConfigurationModelMakerFactoryTDB.java new file mode 100644 index 000000000..ac5aa7368 --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/rdfsetup/impl/tdb/ConfigurationModelMakerFactoryTDB.java @@ -0,0 +1,45 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.servlet.setup.rdfsetup.impl.tdb; + +import com.hp.hpl.jena.rdf.model.ModelMaker; + +import edu.cornell.mannlib.vitro.webapp.dao.jena.RDFServiceModelMaker; +import edu.cornell.mannlib.vitro.webapp.modelaccess.ConfigurationModelMakerFactory; +import edu.cornell.mannlib.vitro.webapp.modelaccess.adapters.ListCachingModelMaker; +import edu.cornell.mannlib.vitro.webapp.modelaccess.adapters.MemoryMappingModelMaker; +import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService; + +/** + * In TDB, is there any difference between short-term and long-term connections? + * + * In any case, memory-map all of the Configuration models, and apply them to + * both short-term and long-term use. + * + * RDFService doesn't support empty models, so support them with ListCaching + */ +public class ConfigurationModelMakerFactoryTDB extends + ConfigurationModelMakerFactory { + + private final ModelMaker longTermModelMaker; + + public ConfigurationModelMakerFactoryTDB(RDFService longTermRdfService) { + this.longTermModelMaker = new ListCachingModelMaker(new MemoryMappingModelMaker( + new RDFServiceModelMaker(longTermRdfService), + CONFIGURATION_MODELS)); + } + + @Override + public ModelMaker getModelMaker(RDFService longTermRdfService) { + return addConfigurationDecorators(longTermModelMaker); + } + + /** + * The long-term models are all memory-mapped, so use them. + */ + @Override + public ModelMaker getShortTermModelMaker(RDFService shortTermRdfService) { + return addConfigurationDecorators(longTermModelMaker); + } + +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/rdfsetup/impl/tdb/ContentModelMakerFactoryTDB.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/rdfsetup/impl/tdb/ContentModelMakerFactoryTDB.java new file mode 100644 index 000000000..3ad84f5ca --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/rdfsetup/impl/tdb/ContentModelMakerFactoryTDB.java @@ -0,0 +1,59 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.servlet.setup.rdfsetup.impl.tdb; + +import com.hp.hpl.jena.rdf.model.ModelMaker; + +import edu.cornell.mannlib.vitro.webapp.dao.jena.RDFServiceModelMaker; +import edu.cornell.mannlib.vitro.webapp.modelaccess.ContentModelMakerFactory; +import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelMakerFactory; +import edu.cornell.mannlib.vitro.webapp.modelaccess.adapters.ListCachingModelMaker; +import edu.cornell.mannlib.vitro.webapp.modelaccess.adapters.MemoryMappingModelMaker; +import edu.cornell.mannlib.vitro.webapp.modelaccess.adapters.ShadowingModelMaker; +import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService; + +/** + * In TDB, is there any difference between short-term and long-term connections? + * + * Anyway, memory-map the small models, and use a short-term connection for the + * others (when available). + * + * RDFService doesn't support empty models, so support them with ListCaching + */ +public class ContentModelMakerFactoryTDB extends ContentModelMakerFactory + implements ModelMakerFactory { + + private final ModelMaker longTermModelMaker; + + public ContentModelMakerFactoryTDB(RDFService longTermRdfService) { + this.longTermModelMaker = new ListCachingModelMaker(new MemoryMappingModelMaker( + new RDFServiceModelMaker(longTermRdfService), + SMALL_CONTENT_MODELS)); + } + + /** + * The small content models (tbox, app_metadata) are memory mapped, for + * speed. + */ + @Override + public ModelMaker getModelMaker(RDFService longTermRdfService) { + return addContentDecorators(longTermModelMaker); + } + + /** + * For short-term use, the large models (abox) will come from a short-term + * service. The small models can be the memory-mapped ones that we created + * for long-term use. + */ + @Override + public ModelMaker getShortTermModelMaker(RDFService shortTermRdfService) { + ModelMaker shortTermModelMaker = new RDFServiceModelMaker( + shortTermRdfService); + + // No need to create a fresh memory map of the small models: use the + // long-term ones. + return addContentDecorators(new ShadowingModelMaker( + shortTermModelMaker, longTermModelMaker, SMALL_CONTENT_MODELS)); + } + +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/rdfsetup/impl/tdb/RDFSourceTDB.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/rdfsetup/impl/tdb/RDFSourceTDB.java new file mode 100644 index 000000000..8cb77cdec --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/rdfsetup/impl/tdb/RDFSourceTDB.java @@ -0,0 +1,85 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.servlet.setup.rdfsetup.impl.tdb; + +import java.io.File; +import java.io.IOException; + +import javax.servlet.ServletContext; +import javax.servlet.ServletContextListener; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import edu.cornell.mannlib.vitro.webapp.config.ConfigurationProperties; +import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelMakerFactory; +import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService; +import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFServiceFactory; +import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.RDFServiceFactorySingle; +import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.jena.tdb.RDFServiceTDB; +import edu.cornell.mannlib.vitro.webapp.servlet.setup.rdfsetup.RDFSource; +import edu.cornell.mannlib.vitro.webapp.startup.StartupStatus; + +/** + * Create the connection to the TDB triple-store. + * + * Create the RDFService on the directory. Create the RDFServiceFactory. + */ +public class RDFSourceTDB implements RDFSource { + private static final Log log = LogFactory.getLog(RDFSourceTDB.class); + + private static final String DIRECTORY_TDB = "tdbModels"; + + private final ConfigurationProperties props; + private final StartupStatus ss; + + private final RDFService rdfService; + private final RDFServiceFactory rdfServiceFactory; + + public RDFSourceTDB(ServletContext ctx, ServletContextListener parent) { + this.props = ConfigurationProperties.getBean(ctx); + this.ss = StartupStatus.getBean(ctx); + + try { + this.rdfService = createRdfService(); + this.rdfServiceFactory = createRDFServiceFactory(); + ss.info(parent, "Initialized the RDF source for TDB"); + } catch (IOException e) { + throw new RuntimeException( + "Failed to set up the RDF source for TDB", e); + } + } + + private RDFService createRdfService() throws IOException { + String vitroHome = props.getProperty("vitro.home"); + String directoryPath = vitroHome + File.separatorChar + DIRECTORY_TDB; + return new RDFServiceTDB(directoryPath); + } + + private RDFServiceFactory createRDFServiceFactory() { + return new RDFServiceFactorySingle(this.rdfService); + } + + @Override + public RDFServiceFactory getRDFServiceFactory() { + return this.rdfServiceFactory; + } + + @Override + public ModelMakerFactory getContentModelMakerFactory() { + return new ContentModelMakerFactoryTDB(this.rdfService); + } + + @Override + public ModelMakerFactory getConfigurationModelMakerFactory() { + return new ConfigurationModelMakerFactoryTDB(this.rdfService); + } + + @Override + public void close() { + if (this.rdfService != null) { + this.rdfService.close(); + } + } + +} diff --git a/webapp/web/WEB-INF/resources/startup_listeners.txt b/webapp/web/WEB-INF/resources/startup_listeners.txt index c36f47a9f..c32c00c29 100644 --- a/webapp/web/WEB-INF/resources/startup_listeners.txt +++ b/webapp/web/WEB-INF/resources/startup_listeners.txt @@ -22,14 +22,11 @@ edu.cornell.mannlib.vitro.webapp.config.RevisionInfoSetup edu.cornell.mannlib.vitro.webapp.email.FreemarkerEmailFactory$Setup -### this listener must be run before SDBSetup, all models setups and WebappDaoSetup ### -edu.cornell.mannlib.vitro.webapp.servlet.setup.JenaPersistentDataSourceSetup - -edu.cornell.mannlib.vitro.webapp.servlet.setup.RDFServiceSetup +# In 1.8, this should replace JenaPersistentDataSourceSetup, RDFServiceSetup, and maybe more. +edu.cornell.mannlib.vitro.webapp.servlet.setup.rdfsetup.RDFSetup edu.cornell.mannlib.vitro.webapp.servlet.setup.ConfigurationModelsSetup edu.cornell.mannlib.vitro.webapp.servlet.setup.ContentModelSetup -edu.cornell.mannlib.vitro.webapp.servlet.setup.ModelMakerSetup edu.cornell.mannlib.vitro.webapp.filestorage.backend.FileStorageSetup