
com.manydesigns.portofino.persistence.Persistence Maven / Gradle / Ivy
/*
* Copyright (C) 2005-2016 ManyDesigns srl. All rights reserved.
* http://www.manydesigns.com/
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 3 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package com.manydesigns.portofino.persistence;
import com.manydesigns.elements.util.ElementsFileUtils;
import com.manydesigns.portofino.PortofinoProperties;
import com.manydesigns.portofino.cache.CacheResetEvent;
import com.manydesigns.portofino.cache.CacheResetListenerRegistry;
import com.manydesigns.portofino.database.platforms.DatabasePlatformsRegistry;
import com.manydesigns.portofino.di.Inject;
import com.manydesigns.portofino.model.Model;
import com.manydesigns.portofino.model.database.*;
import com.manydesigns.portofino.modules.BaseModule;
import com.manydesigns.portofino.modules.DatabaseModule;
import com.manydesigns.portofino.persistence.hibernate.HibernateConfig;
import com.manydesigns.portofino.persistence.hibernate.HibernateDatabaseSetup;
import com.manydesigns.portofino.reflection.TableAccessor;
import com.manydesigns.portofino.sync.DatabaseSyncer;
import liquibase.Contexts;
import liquibase.Liquibase;
import liquibase.database.DatabaseFactory;
import liquibase.database.jvm.JdbcConnection;
import liquibase.resource.FileSystemResourceAccessor;
import liquibase.resource.ResourceAccessor;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.Configuration;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.sql.Connection;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.Map;
/**
* @author Paolo Predonzani - [email protected]
* @author Angelo Lupo - [email protected]
* @author Giampiero Granatella - [email protected]
* @author Alessio Stalla - [email protected]
*/
public class Persistence {
public static final String copyright =
"Copyright (C) 2005-2016, ManyDesigns srl";
//**************************************************************************
// Constants
//**************************************************************************
public static final String APP_DBS_DIR = "dbs";
public static final String APP_MODEL_FILE = "portofino-model.xml";
public final static String changelogFileNameTemplate = "{0}-changelog.xml";
//**************************************************************************
// Fields
//**************************************************************************
protected final DatabasePlatformsRegistry databasePlatformsRegistry;
protected Model model;
protected final Map setups;
protected final File appDir;
protected final File appDbsDir;
protected final File appModelFile;
protected final org.apache.commons.configuration.Configuration configuration;
@Inject(BaseModule.CACHE_RESET_LISTENER_REGISTRY)
public CacheResetListenerRegistry cacheResetListenerRegistry;
//**************************************************************************
// Logging
//**************************************************************************
public static final Logger logger =
LoggerFactory.getLogger(Persistence.class);
//**************************************************************************
// Constructors
//**************************************************************************
public Persistence(
File appDir, org.apache.commons.configuration.Configuration configuration,
DatabasePlatformsRegistry databasePlatformsRegistry) {
this.appDir = appDir;
this.configuration = configuration;
this.databasePlatformsRegistry = databasePlatformsRegistry;
appDbsDir = new File(appDir, APP_DBS_DIR);
logger.info("Application dbs dir: {}",
appDbsDir.getAbsolutePath());
boolean result = ElementsFileUtils.ensureDirectoryExistsAndWarnIfNotWritable(appDbsDir);
appModelFile = new File(appDir, APP_MODEL_FILE);
logger.info("Application model file: {}",
appModelFile.getAbsolutePath());
if (!result) {
throw new Error("Could not initialize application");
}
setups = new HashMap();
}
//**************************************************************************
// Model loading
//**************************************************************************
public synchronized void loadXmlModel() {
logger.info("Loading xml model from file: {}", appModelFile.getAbsolutePath());
try {
JAXBContext jc = JAXBContext.newInstance(Model.JAXB_MODEL_PACKAGES);
Unmarshaller um = jc.createUnmarshaller();
Model model = (Model) um.unmarshal(appModelFile);
File modelDir = getModelDirectory();
for(Database database : model.getDatabases()) {
File databaseDir = new File(modelDir, database.getDatabaseName());
for(Schema schema : database.getSchemas()) {
File schemaDir = new File(databaseDir, schema.getSchemaName());
if(schemaDir.isDirectory()) {
logger.debug("Schema directory {} exists", schemaDir);
File[] tableFiles = schemaDir.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.endsWith(".table.xml");
}
});
for(File tableFile : tableFiles) {
Table table = (Table) um.unmarshal(tableFile);
if(!tableFile.getName().equalsIgnoreCase(table.getTableName() + ".table.xml")) {
throw new Exception("Found table " + table.getTableName() + " defined in file " + tableFile);
}
table.afterUnmarshal(um, schema);
schema.getTables().add(table);
}
} else {
logger.debug("Schema directory {} does not exist", schemaDir);
}
}
}
this.model = model;
initModel();
} catch (Exception e) {
String msg = "Cannot load/parse model: " + appModelFile;
logger.error(msg, e);
}
}
protected File getModelDirectory() {
return new File(appModelFile.getParentFile(), FilenameUtils.getBaseName(appModelFile.getName()));
}
protected void runLiquibase(Database database) {
logger.info("Updating database definitions");
ResourceAccessor resourceAccessor =
new FileSystemResourceAccessor(appDir.getAbsolutePath());
ConnectionProvider connectionProvider =
database.getConnectionProvider();
String databaseName = database.getDatabaseName();
for(Schema schema : database.getSchemas()) {
String schemaName = schema.getSchemaName();
String changelogFileName =
MessageFormat.format(
changelogFileNameTemplate, databaseName + "-" + schemaName);
File changelogFile = new File(appDbsDir, changelogFileName);
if(!changelogFile.isFile()) {
logger.info("Changelog file does not exist or is not a normal file, skipping: {}", changelogFile);
continue;
}
logger.info("Running changelog file: {}", changelogFile);
Connection connection = null;
try {
connection = connectionProvider.acquireConnection();
JdbcConnection jdbcConnection = new JdbcConnection(connection);
liquibase.database.Database lqDatabase =
DatabaseFactory.getInstance().findCorrectDatabaseImplementation(jdbcConnection);
lqDatabase.setDefaultSchemaName(schemaName);
String relativeChangelogPath =
ElementsFileUtils.getRelativePath(appDir, changelogFile, System.getProperty("file.separator"));
if(new File(relativeChangelogPath).isAbsolute()) {
logger.warn("The application dbs dir {} is not inside the apps dir {}; using an absolute path for Liquibase update",
appDbsDir, appDir);
}
Liquibase lq = new Liquibase(
relativeChangelogPath,
resourceAccessor,
lqDatabase);
lq.update((Contexts) null);
} catch (Exception e) {
String msg = "Couldn't update database: " + schemaName;
logger.error(msg, e);
} finally {
connectionProvider.releaseConnection(connection);
}
}
}
public synchronized void saveXmlModel() throws IOException, JAXBException {
//TODO gestire conflitti con modifiche esterne?
File tempFile = File.createTempFile(appModelFile.getName(), "");
JAXBContext jc = JAXBContext.newInstance(Model.JAXB_MODEL_PACKAGES);
Marshaller m = jc.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
m.marshal(model, tempFile);
ElementsFileUtils.moveFileSafely(tempFile, appModelFile.getAbsolutePath());
File modelDir = getModelDirectory();
for(Database database : model.getDatabases()) {
File databaseDir = new File(modelDir, database.getDatabaseName());
for(Schema schema : database.getSchemas()) {
File schemaDir = new File(databaseDir, schema.getSchemaName());
if(schemaDir.isDirectory() || schemaDir.mkdirs()) {
logger.debug("Schema directory {} exists", schemaDir);
File[] tableFiles = schemaDir.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.endsWith(".table.xml");
}
});
for(File tableFile : tableFiles) {
if(!tableFile.delete()) {
logger.warn("Could not delete table file {}", tableFile.getAbsolutePath());
}
}
for(Table table : schema.getTables()) {
File tableFile = new File(schemaDir, table.getTableName() + ".table.xml");
m.marshal(table, tableFile);
}
} else {
logger.debug("Schema directory {} does not exist", schemaDir);
}
}
}
logger.info("Saved xml model to file: {}", appModelFile);
}
public synchronized void initModel() {
logger.info("Cleaning up old setups");
for (Map.Entry current : setups.entrySet()) {
String databaseName = current.getKey();
logger.info("Cleaning up old setup for: {}", databaseName);
HibernateDatabaseSetup hibernateDatabaseSetup = current.getValue();
try {
SessionFactory sessionFactory = hibernateDatabaseSetup.getSessionFactory();
sessionFactory.close();
} catch (Throwable t) {
logger.warn("Cannot close session factory for: " + databaseName, t);
}
}
setups.clear();
model.init();
for (Database database : model.getDatabases()) {
try {
ConnectionProvider connectionProvider = database.getConnectionProvider();
connectionProvider.init(databasePlatformsRegistry);
if (connectionProvider.getStatus()
.equals(ConnectionProvider.STATUS_CONNECTED)) {
HibernateConfig builder =
new HibernateConfig(connectionProvider, configuration);
String trueString = database.getTrueString();
if (trueString != null) {
builder.setTrueString(
"null".equalsIgnoreCase(trueString) ? null : trueString);
}
String falseString = database.getFalseString();
if (falseString != null) {
builder.setFalseString(
"null".equalsIgnoreCase(falseString) ? null : falseString);
}
Configuration configuration =
builder.buildSessionFactory(database);
StandardServiceRegistryBuilder registryBuilder =
new StandardServiceRegistryBuilder().applySettings(configuration.getProperties());
SessionFactory sessionFactory = configuration.buildSessionFactory(registryBuilder.build());
HibernateDatabaseSetup setup =
new HibernateDatabaseSetup(
configuration, sessionFactory);
String databaseName = database.getDatabaseName();
setups.put(databaseName, setup);
}
} catch (Exception e) {
logger.error("Could not create connection provider for " + database);
}
}
cacheResetListenerRegistry.fireReset(new CacheResetEvent(this));
}
//**************************************************************************
// Database stuff
//**************************************************************************
public ConnectionProvider getConnectionProvider(String databaseName) {
for (Database database : model.getDatabases()) {
if (database.getDatabaseName().equals(databaseName)) {
return database.getConnectionProvider();
}
}
return null;
}
public org.apache.commons.configuration.Configuration getPortofinoProperties() {
return configuration;
}
public DatabasePlatformsRegistry getDatabasePlatformsRegistry() {
return databasePlatformsRegistry;
}
//**************************************************************************
// Model access
//**************************************************************************
public Model getModel() {
return model;
}
public synchronized void syncDataModel(String databaseName) throws Exception {
Database sourceDatabase = DatabaseLogic.findDatabaseByName(model, databaseName);
if(configuration.getBoolean(DatabaseModule.LIQUIBASE_ENABLED, true)) {
runLiquibase(sourceDatabase);
} else {
logger.debug("syncDataModel called, but Liquibase is not enabled");
}
ConnectionProvider connectionProvider = sourceDatabase.getConnectionProvider();
DatabaseSyncer dbSyncer = new DatabaseSyncer(connectionProvider);
Database targetDatabase = dbSyncer.syncDatabase(model);
model.getDatabases().remove(sourceDatabase);
model.getDatabases().add(targetDatabase);
}
//**************************************************************************
// Persistance
//**************************************************************************
public Session getSession(String databaseName) {
return ensureDatabaseSetup(databaseName).getThreadSession();
}
protected HibernateDatabaseSetup ensureDatabaseSetup(String databaseName) {
HibernateDatabaseSetup setup = setups.get(databaseName);
if (setup == null) {
throw new Error("No setup exists for database: " + databaseName);
}
return setup;
}
public void closeSessions() {
for (HibernateDatabaseSetup current : setups.values()) {
closeSession(current);
}
}
public void closeSession(String databaseName) {
closeSession(ensureDatabaseSetup(databaseName));
}
protected void closeSession(HibernateDatabaseSetup current) {
Session session = current.getThreadSession(false);
if (session != null) {
try {
Transaction transaction = session.getTransaction();
if(transaction != null && transaction.isActive()) {
transaction.rollback();
}
session.close();
} catch (Throwable e) {
logger.warn("Couldn't close session: " + ExceptionUtils.getRootCauseMessage(e), e);
}
current.removeThreadSession();
}
}
public @NotNull TableAccessor getTableAccessor(String databaseName, String entityName) {
Database database = DatabaseLogic.findDatabaseByName(model, databaseName);
assert database != null;
Table table = DatabaseLogic.findTableByEntityName(database, entityName);
assert table != null;
return new TableAccessor(table);
}
//**************************************************************************
// User
//**************************************************************************
public void start() {
loadXmlModel();
for(Database database : model.getDatabases()) {
runLiquibase(database);
}
}
public void stop() {
for(HibernateDatabaseSetup setup : setups.values()) {
//TODO It is the responsibility of the application to ensure that there are no open Sessions before calling close().
//http://ajava.org/online/hibernate3api/org/hibernate/SessionFactory.html#close%28%29
setup.getSessionFactory().close();
}
for (Database database : model.getDatabases()) {
ConnectionProvider connectionProvider =
database.getConnectionProvider();
connectionProvider.shutdown();
}
}
//**************************************************************************
// App directories and files
//**************************************************************************
public String getName() {
return getPortofinoProperties().getString(PortofinoProperties.APP_NAME);
}
public File getAppDbsDir() {
return appDbsDir;
}
public File getAppModelFile() {
return appModelFile;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy