Java tutorial
/* * Copyright (C) 2005-2013 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.Liquibase; import liquibase.database.DatabaseFactory; import liquibase.database.jvm.JdbcConnection; import liquibase.resource.FileSystemResourceAccessor; import liquibase.resource.ResourceAccessor; import org.apache.commons.lang.exception.ExceptionUtils; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.Transaction; 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.IOException; import java.sql.Connection; import java.text.MessageFormat; import java.util.*; /** * @author Paolo Predonzani - paolo.predonzani@manydesigns.com * @author Angelo Lupo - angelo.lupo@manydesigns.com * @author Giampiero Granatella - giampiero.granatella@manydesigns.com * @author Alessio Stalla - alessio.stalla@manydesigns.com */ public class Persistence { public static final String copyright = "Copyright (c) 2005-2013, 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<String, HibernateDatabaseSetup> 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<String, HibernateDatabaseSetup>(); } //************************************************************************** // 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) um.unmarshal(appModelFile); boolean syncOnStart = false; initModel(); if (configuration.getBoolean(DatabaseModule.LIQUIBASE_ENABLED, true)) { runLiquibaseScripts(); } if (syncOnStart) { List<String> databaseNames = new ArrayList<String>(); for (Database sourceDatabase : model.getDatabases()) { String databaseName = sourceDatabase.getDatabaseName(); databaseNames.add(databaseName); } for (String databaseName : databaseNames) { syncDataModel(databaseName); } initModel(); } } catch (Exception e) { String msg = "Cannot load/parse model: " + appModelFile; logger.error(msg, e); } } protected Date lastLiquibaseRunTime = new Date(0); protected void runLiquibaseScripts() { logger.info("Updating database definitions"); ResourceAccessor resourceAccessor = new FileSystemResourceAccessor(appDir.getAbsolutePath()); for (Database database : model.getDatabases()) { 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; } if (changelogFile.lastModified() <= lastLiquibaseRunTime.getTime()) { logger.info("Changelog file not modified since last reload, 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(null); } catch (Exception e) { String msg = "Couldn't update database: " + schemaName; logger.error(msg, e); } finally { connectionProvider.releaseConnection(connection); } } } lastLiquibaseRunTime = new Date(); } 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()); lastLiquibaseRunTime = new Date(0); logger.info("Saved xml model to file: {}", appModelFile); } public synchronized void initModel() { if (setups != null) { logger.info("Cleaning up old setups"); for (Map.Entry<String, HibernateDatabaseSetup> 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()) { 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); SessionFactory sessionFactory = configuration.buildSessionFactory(); HibernateDatabaseSetup setup = new HibernateDatabaseSetup(configuration, sessionFactory); String databaseName = database.getDatabaseName(); setups.put(databaseName, setup); } } 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 void syncDataModel(String databaseName) throws Exception { if (!configuration.getBoolean(DatabaseModule.LIQUIBASE_ENABLED, true)) { logger.warn("syncDataModel called, but Liquibase is not enabled"); return; } Database sourceDatabase = DatabaseLogic.findDatabaseByName(model, databaseName); 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 shutdown() { 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; } }