001/* 002 * Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the Eclipse Public License v1.0 006 * which accompanies this distribution, and is available at 007 * http://www.eclipse.org/legal/epl-v10.html 008 * 009 * Contributors: 010 * Florent Guillaume 011 */ 012 013package org.nuxeo.ecm.core.storage.sql.jdbc; 014 015import java.sql.Connection; 016import java.sql.SQLException; 017import java.util.Map.Entry; 018 019import javax.naming.NamingException; 020import javax.sql.DataSource; 021import javax.sql.XAConnection; 022import javax.sql.XADataSource; 023 024import org.apache.commons.beanutils.BeanUtils; 025import org.apache.commons.logging.Log; 026import org.apache.commons.logging.LogFactory; 027import org.nuxeo.ecm.core.api.NuxeoException; 028import org.nuxeo.ecm.core.storage.sql.ClusterInvalidator; 029import org.nuxeo.ecm.core.storage.sql.Mapper; 030import org.nuxeo.ecm.core.storage.sql.Model; 031import org.nuxeo.ecm.core.storage.sql.Model.IdType; 032import org.nuxeo.ecm.core.storage.sql.ModelSetup; 033import org.nuxeo.ecm.core.storage.sql.RepositoryBackend; 034import org.nuxeo.ecm.core.storage.sql.RepositoryDescriptor; 035import org.nuxeo.ecm.core.storage.sql.RepositoryImpl; 036import org.nuxeo.ecm.core.storage.sql.Session.PathResolver; 037import org.nuxeo.ecm.core.storage.sql.jdbc.dialect.Dialect; 038import org.nuxeo.runtime.api.Framework; 039import org.nuxeo.runtime.datasource.ConnectionHelper; 040import org.nuxeo.runtime.datasource.DataSourceHelper; 041import org.nuxeo.runtime.datasource.PooledDataSourceRegistry.PooledDataSource; 042 043/** 044 * JDBC Backend for a repository. 045 */ 046public class JDBCBackend implements RepositoryBackend { 047 048 private static final Log log = LogFactory.getLog(JDBCBackend.class); 049 050 private RepositoryImpl repository; 051 052 private String pseudoDataSourceName; 053 054 private XADataSource xadatasource; 055 056 private Dialect dialect; 057 058 private SQLInfo sqlInfo; 059 060 private boolean firstMapper = true; 061 062 private ClusterInvalidator clusterInvalidator; 063 064 private boolean isPooledDataSource; 065 066 @Override 067 public void initialize(RepositoryImpl repository) { 068 this.repository = repository; 069 RepositoryDescriptor repositoryDescriptor = repository.getRepositoryDescriptor(); 070 pseudoDataSourceName = ConnectionHelper.getPseudoDataSourceNameForRepository(repositoryDescriptor.name); 071 072 try { 073 DataSource ds = DataSourceHelper.getDataSource(pseudoDataSourceName); 074 if (ds instanceof PooledDataSource) { 075 isPooledDataSource = true; 076 return; 077 } 078 } catch (NamingException cause) {; 079 } 080 081 // try single-datasource non-XA mode 082 try (Connection connection = ConnectionHelper.getConnection(pseudoDataSourceName)) { 083 if (connection != null) { 084 return; 085 } 086 } catch (SQLException cause) { 087 throw new NuxeoException("Connection error", cause); 088 } 089 090 // standard XA mode 091 // instantiate the XA datasource 092 String className = repositoryDescriptor.xaDataSourceName; 093 Class<?> klass; 094 try { 095 klass = Class.forName(className); 096 } catch (ClassNotFoundException e) { 097 throw new NuxeoException("Unknown class: " + className, e); 098 } 099 Object instance; 100 try { 101 instance = klass.newInstance(); 102 } catch (ReflectiveOperationException e) { 103 throw new NuxeoException("Cannot instantiate class: " + className, e); 104 } 105 if (!(instance instanceof XADataSource)) { 106 throw new NuxeoException("Not a XADataSource: " + className); 107 } 108 xadatasource = (XADataSource) instance; 109 110 // set JavaBean properties on the datasource 111 for (Entry<String, String> entry : repositoryDescriptor.properties.entrySet()) { 112 String name = entry.getKey(); 113 Object value = Framework.expandVars(entry.getValue()); 114 if (name.contains("/")) { 115 // old syntax where non-String types were explicited 116 name = name.substring(0, name.indexOf('/')); 117 } 118 // transform to proper JavaBean convention 119 if (Character.isLowerCase(name.charAt(1))) { 120 name = Character.toLowerCase(name.charAt(0)) + name.substring(1); 121 } 122 try { 123 BeanUtils.setProperty(xadatasource, name, value); 124 } catch (ReflectiveOperationException e) { 125 log.error(String.format("Cannot set %s = %s", name, value)); 126 } 127 } 128 } 129 130 /** 131 * {@inheritDoc} 132 * <p> 133 * Opens a connection to get the dialect and finish initializing the {@link ModelSetup}. 134 */ 135 @Override 136 public void initializeModelSetup(ModelSetup modelSetup) { 137 try { 138 XAConnection xaconnection = null; 139 // try single-datasource non-XA mode 140 Connection connection = ConnectionHelper.getConnection(pseudoDataSourceName); 141 try { 142 if (connection == null) { 143 // standard XA mode 144 xaconnection = xadatasource.getXAConnection(); 145 connection = xaconnection.getConnection(); 146 } 147 dialect = Dialect.createDialect(connection, repository.getRepositoryDescriptor()); 148 } finally { 149 if (connection != null) { 150 connection.close(); 151 } 152 if (xaconnection != null) { 153 xaconnection.close(); 154 } 155 } 156 } catch (SQLException cause) { 157 throw new NuxeoException("Cannot connect to database", cause); 158 } 159 modelSetup.materializeFulltextSyntheticColumn = dialect.getMaterializeFulltextSyntheticColumn(); 160 modelSetup.supportsArrayColumns = dialect.supportsArrayColumns(); 161 switch (dialect.getIdType()) { 162 case VARCHAR: 163 case UUID: 164 modelSetup.idType = IdType.STRING; 165 break; 166 case SEQUENCE: 167 modelSetup.idType = IdType.LONG; 168 break; 169 default: 170 throw new AssertionError(dialect.getIdType().toString()); 171 } 172 } 173 174 /** 175 * {@inheritDoc} 176 * <p> 177 * Creates the {@link SQLInfo} from the model and the dialect. 178 */ 179 @Override 180 public void initializeModel(Model model) { 181 sqlInfo = new SQLInfo(model, dialect); 182 } 183 184 @Override 185 public void setClusterInvalidator(ClusterInvalidator clusterInvalidator) { 186 this.clusterInvalidator = clusterInvalidator; 187 } 188 189 @Override 190 public Mapper newMapper(Model model, PathResolver pathResolver, boolean useInvalidations) { 191 boolean noSharing = !useInvalidations; 192 RepositoryDescriptor repositoryDescriptor = repository.getRepositoryDescriptor(); 193 194 ClusterInvalidator cnh = useInvalidations ? clusterInvalidator : null; 195 Mapper mapper = new JDBCMapper(model, pathResolver, sqlInfo, xadatasource, cnh, noSharing, repository); 196 if (isPooledDataSource) { 197 mapper = JDBCMapperConnector.newConnector(mapper); 198 if (noSharing) { 199 mapper = JDBCMapperTxSuspender.newConnector(mapper); 200 } 201 } else { 202 mapper.connect(); 203 } 204 if (firstMapper) { 205 firstMapper = false; 206 if (repositoryDescriptor.getNoDDL()) { 207 log.info("Skipping database creation"); 208 } else { 209 // first connection, initialize the database 210 mapper.createDatabase(); 211 } 212 if (log.isDebugEnabled()) { 213 log.debug(String.format("Database ready, fulltext: disabled=%b searchDisabled=%b.", 214 repositoryDescriptor.getFulltextDisabled(), repositoryDescriptor.getFulltextSearchDisabled())); 215 } 216 } 217 return mapper; 218 } 219 220 @Override 221 public void shutdown() { 222 if (clusterInvalidator != null) { 223 clusterInvalidator.close(); 224 } 225 } 226 227}