/* * Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) and others. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Florent Guillaume */ package org.nuxeo.ecm.core.storage.sql.jdbc; import java.sql.Connection; import java.sql.SQLException; import java.util.Map.Entry; import javax.naming.NamingException; import javax.resource.ResourceException; import javax.sql.DataSource; import javax.sql.XAConnection; import javax.sql.XADataSource; import org.apache.commons.beanutils.BeanUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.nuxeo.ecm.core.storage.StorageException; import org.nuxeo.ecm.core.storage.sql.Mapper; import org.nuxeo.ecm.core.storage.sql.Model; import org.nuxeo.ecm.core.storage.sql.Model.IdType; import org.nuxeo.ecm.core.storage.sql.ModelSetup; import org.nuxeo.ecm.core.storage.sql.RepositoryBackend; import org.nuxeo.ecm.core.storage.sql.RepositoryDescriptor; import org.nuxeo.ecm.core.storage.sql.RepositoryImpl; import org.nuxeo.ecm.core.storage.sql.Session.PathResolver; import org.nuxeo.ecm.core.storage.sql.jdbc.dialect.Dialect; import org.nuxeo.runtime.api.Framework; import org.nuxeo.runtime.datasource.ConnectionHelper; import org.nuxeo.runtime.datasource.DataSourceHelper; import org.nuxeo.runtime.datasource.PooledDataSourceRegistry.PooledDataSource; /** * JDBC Backend for a repository. */ public class JDBCBackend implements RepositoryBackend { private static final Log log = LogFactory.getLog(JDBCBackend.class); private RepositoryImpl repository; private String pseudoDataSourceName; private XADataSource xadatasource; private Dialect dialect; private SQLInfo sqlInfo; private boolean firstMapper = true; private ClusterNodeHandler clusterNodeHandler; private JDBCConnectionPropagator connectionPropagator; public JDBCBackend() { connectionPropagator = new JDBCConnectionPropagator(); } protected boolean isPooledDataSource; @Override public void initialize(RepositoryImpl repository) throws StorageException { this.repository = repository; RepositoryDescriptor repositoryDescriptor = repository.getRepositoryDescriptor(); pseudoDataSourceName = ConnectionHelper.getPseudoDataSourceNameForRepository(repositoryDescriptor.name); try { DataSource ds = DataSourceHelper.getDataSource(pseudoDataSourceName); if (ds instanceof PooledDataSource) { isPooledDataSource = true; return; } } catch (NamingException cause) { ; } // try single-datasource non-XA mode try (Connection connection = ConnectionHelper.getConnection(pseudoDataSourceName)) { if (connection != null) { return; } } catch (SQLException cause) { throw new StorageException("Connection error", cause); } // standard XA mode // instantiate the XA datasource String className = repositoryDescriptor.xaDataSourceName; Class<?> klass; try { klass = Class.forName(className); } catch (ClassNotFoundException e) { throw new StorageException("Unknown class: " + className, e); } Object instance; try { instance = klass.newInstance(); } catch (Exception e) { throw new StorageException( "Cannot instantiate class: " + className, e); } if (!(instance instanceof XADataSource)) { throw new StorageException("Not a XADataSource: " + className); } xadatasource = (XADataSource) instance; // set JavaBean properties on the datasource for (Entry<String, String> entry : repositoryDescriptor.properties.entrySet()) { String name = entry.getKey(); Object value = Framework.expandVars(entry.getValue()); if (name.contains("/")) { // old syntax where non-String types were explicited name = name.substring(0, name.indexOf('/')); } // transform to proper JavaBean convention if (Character.isLowerCase(name.charAt(1))) { name = Character.toLowerCase(name.charAt(0)) + name.substring(1); } try { BeanUtils.setProperty(xadatasource, name, value); } catch (Exception e) { log.error(String.format("Cannot set %s = %s", name, value)); } } } /** * {@inheritDoc} * <p> * Opens a connection to get the dialect and finish initializing the * {@link ModelSetup}. */ @Override public void initializeModelSetup(ModelSetup modelSetup) throws StorageException { try { XAConnection xaconnection = null; // try single-datasource non-XA mode Connection connection = ConnectionHelper.getConnection(pseudoDataSourceName); try { if (connection == null) { // standard XA mode xaconnection = xadatasource.getXAConnection(); connection = xaconnection.getConnection(); } dialect = Dialect.createDialect(connection, repository.getBinaryManager(), repository.getRepositoryDescriptor()); } finally { if (connection != null) { connection.close(); } if (xaconnection != null) { xaconnection.close(); } } } catch (SQLException | ResourceException cause) { throw new StorageException("Cannot connect to database", cause); } modelSetup.materializeFulltextSyntheticColumn = dialect.getMaterializeFulltextSyntheticColumn(); modelSetup.supportsArrayColumns = dialect.supportsArrayColumns(); switch (dialect.getIdType()) { case VARCHAR: case UUID: modelSetup.idType = IdType.STRING; break; case SEQUENCE: modelSetup.idType = IdType.LONG; break; default: throw new AssertionError(dialect.getIdType().toString()); } } /** * {@inheritDoc} * <p> * Creates the {@link SQLInfo} from the model and the dialect. */ @Override public void initializeModel(Model model) throws StorageException { sqlInfo = new SQLInfo(model, dialect); } @Override public Mapper newMapper(Model model, PathResolver pathResolver, MapperKind kind) throws StorageException { boolean noSharing = kind == MapperKind.LOCK_MANAGER || kind == MapperKind.CLUSTER_NODE_HANDLER; boolean noInvalidationPropagation = kind == MapperKind.LOCK_MANAGER; RepositoryDescriptor repositoryDescriptor = repository.getRepositoryDescriptor(); ClusterNodeHandler cnh = noInvalidationPropagation ? null : clusterNodeHandler; Mapper mapper = new JDBCMapper(model, pathResolver, sqlInfo, xadatasource, cnh, connectionPropagator, noSharing, repository); if (isPooledDataSource) { mapper = JDBCMapperConnector.newConnector(mapper); if (noSharing) { mapper = JDBCMapperTxSuspender.newConnector(mapper); } } else { mapper.connect(); } if (firstMapper) { firstMapper = false; if (repositoryDescriptor.getNoDDL()) { log.info("Skipping database creation"); } else { // first connection, initialize the database mapper.createDatabase(); } } if (kind == MapperKind.CLUSTER_NODE_HANDLER) { clusterNodeHandler = new ClusterNodeHandler(mapper, repositoryDescriptor); connectionPropagator.setClusterNodeHandler(clusterNodeHandler); } return mapper; } @Override public void shutdown() throws StorageException { if (clusterNodeHandler != null) { clusterNodeHandler.close(); } } }