001/*
002 * (C) Copyright 2006-2015 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 */
019package org.nuxeo.ecm.core.test;
020
021import static org.junit.Assert.assertNotNull;
022
023import java.net.URL;
024import java.net.UnknownHostException;
025import java.sql.SQLException;
026import java.util.Arrays;
027import java.util.Collections;
028import java.util.List;
029
030import org.apache.commons.logging.Log;
031import org.apache.commons.logging.LogFactory;
032import org.nuxeo.ecm.core.api.NuxeoException;
033import org.nuxeo.ecm.core.storage.dbs.DBSHelper;
034import org.nuxeo.ecm.core.storage.mongodb.MongoDBRepository;
035import org.nuxeo.ecm.core.storage.mongodb.MongoDBRepositoryDescriptor;
036import org.nuxeo.ecm.core.storage.sql.DatabaseDB2;
037import org.nuxeo.ecm.core.storage.sql.DatabaseDerby;
038import org.nuxeo.ecm.core.storage.sql.DatabaseH2;
039import org.nuxeo.ecm.core.storage.sql.DatabaseHelper;
040import org.nuxeo.ecm.core.storage.sql.DatabaseMySQL;
041import org.nuxeo.ecm.core.storage.sql.DatabaseOracle;
042import org.nuxeo.ecm.core.storage.sql.DatabasePostgreSQL;
043import org.nuxeo.ecm.core.storage.sql.DatabaseSQLServer;
044import org.nuxeo.runtime.api.Framework;
045import org.nuxeo.runtime.test.runner.FeaturesRunner;
046import org.nuxeo.runtime.test.runner.RuntimeFeature;
047import org.nuxeo.runtime.test.runner.RuntimeHarness;
048import org.osgi.framework.Bundle;
049
050import com.mongodb.BasicDBObject;
051import com.mongodb.DBCollection;
052import com.mongodb.MongoClient;
053
054/**
055 * Description of the specific capabilities of a repository for tests, and helper methods.
056 *
057 * @since 7.3
058 */
059public class StorageConfiguration {
060
061    private static final Log log = LogFactory.getLog(StorageConfiguration.class);
062
063    public static final String CORE_PROPERTY = "nuxeo.test.core";
064
065    public static final String CORE_VCS = "vcs";
066
067    public static final String CORE_MEM = "mem";
068
069    public static final String CORE_MONGODB = "mongodb";
070
071    public static final String CORE_MARKLOGIC = "marklogic";
072
073    public static final String DEFAULT_CORE = CORE_VCS;
074
075    private static final String MONGODB_SERVER_PROPERTY = "nuxeo.test.mongodb.server";
076
077    private static final String MONGODB_DBNAME_PROPERTY = "nuxeo.test.mongodb.dbname";
078
079    public static final String DEFAULT_MONGODB_SERVER = "localhost:27017";
080
081    public static final String DEFAULT_MONGODB_DBNAME = "unittests";
082
083    private static final String CHANGE_TOKEN_ENABLED_PROPERTY = "nuxeo.test.changetoken.enabled";
084
085    private static final String CHANGE_TOKEN_ENABLED_DEFAULT = "true";
086
087    private String coreType;
088
089    private boolean isVCS;
090
091    private boolean isDBS;
092
093    private DatabaseHelper databaseHelper;
094
095    private DBSHelper dbsHelper;
096
097    final CoreFeature feature;
098
099    private boolean changeTokenEnabled;
100
101    protected String mongoDBServer;
102
103    protected String mongoDBDbName;
104
105    public StorageConfiguration(CoreFeature feature) {
106        coreType = defaultSystemProperty(CORE_PROPERTY, DEFAULT_CORE);
107        this.feature = feature;
108    }
109
110    public static String defaultSystemProperty(String name, String def) {
111        String value = System.getProperty(name);
112        if (value == null || value.equals("") || value.equals("${" + name + "}")) {
113            System.setProperty(name, value = def);
114        }
115        return value;
116    }
117
118    protected static String defaultProperty(String name, String def) {
119        String value = System.getProperty(name);
120        if (value == null || value.equals("") || value.equals("${" + name + "}")) {
121            value = def;
122        }
123        Framework.getProperties().setProperty(name, value);
124        return value;
125    }
126
127    protected void init() {
128        changeTokenEnabled = Boolean.parseBoolean(
129                defaultProperty(CHANGE_TOKEN_ENABLED_PROPERTY, CHANGE_TOKEN_ENABLED_DEFAULT));
130        initJDBC();
131        switch (coreType) {
132        case CORE_VCS:
133            isVCS = true;
134            break;
135        case CORE_MEM:
136            isDBS = true;
137            break;
138        case CORE_MONGODB:
139            isDBS = true;
140            initMongoDB();
141            break;
142        default:
143            isDBS = true;
144            initExternal();
145        }
146    }
147
148    public void initJDBC() {
149        databaseHelper = DatabaseHelper.DATABASE;
150
151        String msg = "Deploying JDBC using " + databaseHelper.getClass().getSimpleName();
152        // System.out used on purpose, don't remove
153        System.out.println(getClass().getSimpleName() + ": " + msg);
154        log.info(msg);
155
156        // setup system properties for generic XML extension points
157        // this is used both for VCS (org.nuxeo.ecm.core.storage.sql.RepositoryService)
158        // and DataSources (org.nuxeo.runtime.datasource) extension points
159        try {
160            databaseHelper.setUp();
161        } catch (SQLException e) {
162            throw new NuxeoException(e);
163        }
164    }
165
166    protected void initMongoDB() {
167        mongoDBServer = defaultProperty(MONGODB_SERVER_PROPERTY, DEFAULT_MONGODB_SERVER);
168        mongoDBDbName = defaultProperty(MONGODB_DBNAME_PROPERTY, DEFAULT_MONGODB_DBNAME);
169        MongoDBRepositoryDescriptor descriptor = new MongoDBRepositoryDescriptor();
170        descriptor.name = getRepositoryName();
171        descriptor.server = mongoDBServer;
172        descriptor.dbname = mongoDBDbName;
173        try {
174            clearMongoDB(descriptor);
175        } catch (UnknownHostException e) {
176            throw new NuxeoException(e);
177        }
178    }
179
180    protected void clearMongoDB(MongoDBRepositoryDescriptor descriptor) throws UnknownHostException {
181        MongoClient mongoClient = MongoDBRepository.newMongoClient(descriptor);
182        try {
183            DBCollection coll = MongoDBRepository.getCollection(descriptor, mongoClient);
184            coll.dropIndexes();
185            coll.remove(new BasicDBObject());
186            coll = MongoDBRepository.getCountersCollection(descriptor, mongoClient);
187            coll.dropIndexes();
188            coll.remove(new BasicDBObject());
189        } finally {
190            mongoClient.close();
191        }
192    }
193
194    protected void initExternal() {
195        // Get DBSHelper by reflection
196        String className = String.format("org.nuxeo.ecm.core.storage.%s.DBSHelperImpl", coreType);
197        try {
198            dbsHelper = (DBSHelper) Class.forName(className).newInstance();
199            dbsHelper.init();
200        } catch (ReflectiveOperationException e) {
201            throw new NuxeoException("DBSHelperImpl not found: " + className, e);
202        }
203    }
204
205    public boolean isVCS() {
206        return isVCS;
207    }
208
209    public boolean isVCSH2() {
210        return isVCS && databaseHelper instanceof DatabaseH2;
211    }
212
213    public boolean isVCSDerby() {
214        return isVCS && databaseHelper instanceof DatabaseDerby;
215    }
216
217    public boolean isVCSPostgreSQL() {
218        return isVCS && databaseHelper instanceof DatabasePostgreSQL;
219    }
220
221    public boolean isVCSMySQL() {
222        return isVCS && databaseHelper instanceof DatabaseMySQL;
223    }
224
225    public boolean isVCSOracle() {
226        return isVCS && databaseHelper instanceof DatabaseOracle;
227    }
228
229    public boolean isVCSSQLServer() {
230        return isVCS && databaseHelper instanceof DatabaseSQLServer;
231    }
232
233    public boolean isVCSDB2() {
234        return isVCS && databaseHelper instanceof DatabaseDB2;
235    }
236
237    public boolean isDBS() {
238        return isDBS;
239    }
240
241    public boolean isDBSMem() {
242        return isDBS && CORE_MEM.equals(coreType);
243    }
244
245    public boolean isDBSMongoDB() {
246        return isDBS && CORE_MONGODB.equals(coreType);
247    }
248
249    public boolean isDBSExternal() {
250        return dbsHelper != null;
251    }
252
253    public boolean isDBSMarkLogic() {
254        return isDBS && CORE_MARKLOGIC.equals(coreType);
255    }
256
257    public String getRepositoryName() {
258        return "test";
259    }
260
261    /**
262     * For databases that do asynchronous fulltext indexing, sleep a bit.
263     */
264    public void sleepForFulltext() {
265        if (isVCS()) {
266            databaseHelper.sleepForFulltext();
267        } else {
268            // DBS
269        }
270    }
271
272    /**
273     * Sleep a bit to get to the next millisecond, to have different timestamps.
274     */
275    public void maybeSleepToNextSecond() {
276        try {
277            Thread.sleep(1); // 1 millisecond
278        } catch (InterruptedException e) {
279            Thread.currentThread().interrupt(); // restore interrupted status
280            throw new RuntimeException(e);
281        }
282    }
283
284    public void waitForAsyncCompletion() {
285        feature.waitForAsyncCompletion();
286    }
287
288    public void waitForFulltextIndexing() {
289        waitForAsyncCompletion();
290        sleepForFulltext();
291    }
292
293    /**
294     * Checks if the database supports multiple fulltext indexes.
295     */
296    public boolean supportsMultipleFulltextIndexes() {
297        if (isVCS()) {
298            return databaseHelper.supportsMultipleFulltextIndexes();
299        } else {
300            return false; // DBS
301        }
302    }
303
304    public List<String> getExternalBundles() {
305        if (isDBSExternal()) {
306            return Arrays.asList(String.format("org.nuxeo.ecm.core.storage.%s", coreType),
307                    String.format("org.nuxeo.ecm.core.storage.%s.test", coreType));
308        }
309        return Collections.emptyList();
310    }
311
312    public URL getBlobManagerContrib(FeaturesRunner runner) {
313        String bundleName = "org.nuxeo.ecm.core.test";
314        String contribPath = "OSGI-INF/test-storage-blob-contrib.xml";
315        RuntimeHarness harness = runner.getFeature(RuntimeFeature.class).getHarness();
316        Bundle bundle = harness.getOSGiAdapter().getRegistry().getBundle(bundleName);
317        URL contribURL = bundle.getEntry(contribPath);
318        assertNotNull("deployment contrib " + contribPath + " not found", contribURL);
319        return contribURL;
320    }
321
322    public URL getRepositoryContrib(FeaturesRunner runner) {
323        String msg;
324        if (isVCS()) {
325            msg = "Deploying a VCS repository";
326        } else if (isDBS()) {
327            msg = "Deploying a DBS repository using " + coreType;
328        } else {
329            throw new NuxeoException("Unkown test configuration (not vcs/dbs)");
330        }
331        // System.out used on purpose, don't remove
332        System.out.println(getClass().getSimpleName() + ": " + msg);
333        log.info(msg);
334
335        String contribPath;
336        String bundleName;
337        if (isVCS()) {
338            bundleName = "org.nuxeo.ecm.core.storage.sql.test";
339            contribPath = databaseHelper.getDeploymentContrib();
340        } else {
341            bundleName = "org.nuxeo.ecm.core.test";
342            if (isDBSMem()) {
343                contribPath = "OSGI-INF/test-storage-repo-mem-contrib.xml";
344            } else if (isDBSMongoDB()) {
345                contribPath = "OSGI-INF/test-storage-repo-mongodb-contrib.xml";
346            } else if (isDBSExternal()) {
347                bundleName = String.format("org.nuxeo.ecm.core.storage.%s.test", coreType);
348                contribPath = "OSGI-INF/test-storage-repo-contrib.xml";
349            } else {
350                throw new NuxeoException("Unkown DBS test configuration (not mem/mongodb)");
351            }
352        }
353        RuntimeHarness harness = runner.getFeature(RuntimeFeature.class).getHarness();
354        Bundle bundle = harness.getOSGiAdapter().getRegistry().getBundle(bundleName);
355        URL contribURL = bundle.getEntry(contribPath);
356        assertNotNull("deployment contrib " + contribPath + " not found", contribURL);
357        return contribURL;
358    }
359
360    public boolean isChangeTokenEnabled() {
361        return changeTokenEnabled;
362    }
363
364    /**
365     * @since 9.2
366     */
367    public String getCoreType() {
368        return coreType;
369    }
370
371    /**
372     * @since 9.2
373     */
374    public String getMongoDBServer() {
375        return mongoDBServer;
376    }
377
378    /**
379     * @since 9.2
380     */
381    public String getMongoDBDbName() {
382        return mongoDBDbName;
383    }
384
385}