001/*
002 * (C) Copyright 2006-2016 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;
027
028import javax.naming.NamingException;
029import javax.sql.DataSource;
030
031import org.apache.commons.logging.Log;
032import org.apache.commons.logging.LogFactory;
033import org.nuxeo.ecm.core.api.NuxeoException;
034import org.nuxeo.ecm.core.storage.FulltextDescriptor;
035import org.nuxeo.ecm.core.storage.sql.ClusterInvalidator;
036import org.nuxeo.ecm.core.storage.sql.Mapper;
037import org.nuxeo.ecm.core.storage.sql.Model;
038import org.nuxeo.ecm.core.storage.sql.Model.IdType;
039import org.nuxeo.ecm.core.storage.sql.ModelSetup;
040import org.nuxeo.ecm.core.storage.sql.RepositoryBackend;
041import org.nuxeo.ecm.core.storage.sql.RepositoryDescriptor;
042import org.nuxeo.ecm.core.storage.sql.RepositoryImpl;
043import org.nuxeo.ecm.core.storage.sql.Session.PathResolver;
044import org.nuxeo.ecm.core.storage.sql.jdbc.dialect.Dialect;
045import org.nuxeo.runtime.datasource.ConnectionHelper;
046import org.nuxeo.runtime.datasource.DataSourceHelper;
047import org.nuxeo.runtime.datasource.PooledDataSourceRegistry.PooledDataSource;
048
049/**
050 * JDBC Backend for a repository.
051 */
052public class JDBCBackend implements RepositoryBackend {
053
054    private static final Log log = LogFactory.getLog(JDBCBackend.class);
055
056    private RepositoryImpl repository;
057
058    private Dialect dialect;
059
060    private SQLInfo sqlInfo;
061
062    private boolean firstMapper = true;
063
064    private Boolean initialized;
065
066    private ClusterInvalidator clusterInvalidator;
067
068    private boolean isPooledDataSource;
069
070    @Override
071    public void initialize(RepositoryImpl repository) {
072        this.repository = repository;
073        String dataSourceName = getDataSourceName();
074
075        try {
076            DataSource ds = DataSourceHelper.getDataSource(dataSourceName);
077            if (ds instanceof PooledDataSource) {
078                isPooledDataSource = true;
079            }
080        } catch (NamingException cause) {
081            throw new NuxeoException("Cannot acquire datasource: " + dataSourceName, cause);
082        }
083
084        // check early that the connection is valid
085        try (Connection connection = ConnectionHelper.getConnection(dataSourceName)) {
086            // do nothing, just acquire it to test
087        } catch (SQLException cause) {
088            throw new NuxeoException("Cannot get connection from datasource: " + dataSourceName, cause);
089        }
090    }
091
092    /**
093     * {@inheritDoc}
094     * <p>
095     * Opens a connection to get the dialect and finish initializing the {@link ModelSetup}.
096     */
097    @Override
098    public void initializeModelSetup(ModelSetup modelSetup) {
099        String dataSourceName = getDataSourceName();
100        try (Connection connection = ConnectionHelper.getConnection(dataSourceName)) {
101            dialect = Dialect.createDialect(connection, repository.getRepositoryDescriptor());
102        } catch (SQLException cause) {
103            throw new NuxeoException("Cannot connect to database", cause);
104        }
105        modelSetup.materializeFulltextSyntheticColumn = dialect.getMaterializeFulltextSyntheticColumn();
106        modelSetup.supportsArrayColumns = dialect.supportsArrayColumns();
107        switch (dialect.getIdType()) {
108        case VARCHAR:
109        case UUID:
110            modelSetup.idType = IdType.STRING;
111            break;
112        case SEQUENCE:
113            modelSetup.idType = IdType.LONG;
114            break;
115        default:
116            throw new AssertionError(dialect.getIdType().toString());
117        }
118    }
119
120    protected String getDataSourceName() {
121        RepositoryDescriptor repositoryDescriptor = repository.getRepositoryDescriptor();
122        return JDBCConnection.getDataSourceName(repositoryDescriptor.name);
123    }
124
125    /**
126     * {@inheritDoc}
127     * <p>
128     * Creates the {@link SQLInfo} from the model and the dialect.
129     */
130    @Override
131    public void initializeModel(Model model) {
132        sqlInfo = new SQLInfo(model, dialect);
133    }
134
135    @Override
136    public void setClusterInvalidator(ClusterInvalidator clusterInvalidator) {
137        this.clusterInvalidator = clusterInvalidator;
138    }
139
140    @Override
141    public Mapper newMapper(Model model, PathResolver pathResolver, boolean useInvalidations) {
142        boolean noSharing = !useInvalidations;
143        RepositoryDescriptor repositoryDescriptor = repository.getRepositoryDescriptor();
144
145        ClusterInvalidator cnh = useInvalidations ? clusterInvalidator : null;
146        Mapper mapper = new JDBCMapper(model, pathResolver, sqlInfo, cnh, repository);
147        if (isPooledDataSource) {
148            mapper = JDBCMapperConnector.newConnector(mapper, noSharing);
149        } else {
150            mapper.connect(false);
151        }
152        String repositoryName = repository.getName();
153        if (FALSE.equals(initialized)) {
154            throw new NuxeoException("Database initialization failed previously for: " + repositoryName);
155        }
156        if (firstMapper) {
157            initialized = FALSE;
158            firstMapper = false;
159            String ddlMode = repositoryDescriptor.getDDLMode();
160            if (ddlMode == null) {
161                // compat
162                ddlMode = repositoryDescriptor.getNoDDL() ? RepositoryDescriptor.DDL_MODE_IGNORE
163                        : RepositoryDescriptor.DDL_MODE_EXECUTE;
164            }
165            if (ddlMode.equals(RepositoryDescriptor.DDL_MODE_IGNORE)) {
166                log.info("Skipping database creation");
167            } else {
168                // first connection, initialize the database
169                mapper.createDatabase(ddlMode);
170            }
171            if (log.isDebugEnabled()) {
172                FulltextDescriptor fulltextDescriptor = repositoryDescriptor.getFulltextDescriptor();
173                log.debug(String.format("Database ready, fulltext: disabled=%b searchDisabled=%b.",
174                        fulltextDescriptor.getFulltextDisabled(), fulltextDescriptor.getFulltextSearchDisabled()));
175            }
176            initialized = TRUE;
177        }
178        return mapper;
179    }
180
181    @Override
182    public void shutdown() {
183        if (clusterInvalidator != null) {
184            clusterInvalidator.close();
185        }
186    }
187
188}