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