001/*
002 * (C) Copyright 2014-2018 Nuxeo (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.storage.dbs;
020
021import java.util.ArrayDeque;
022import java.util.Collection;
023import java.util.Deque;
024import java.util.HashSet;
025import java.util.List;
026import java.util.Set;
027
028import org.apache.commons.lang3.StringUtils;
029import org.apache.commons.logging.Log;
030import org.apache.commons.logging.LogFactory;
031import org.nuxeo.ecm.core.api.Lock;
032import org.nuxeo.ecm.core.api.NuxeoException;
033import org.nuxeo.ecm.core.api.lock.LockManager;
034import org.nuxeo.ecm.core.api.repository.FulltextConfiguration;
035import org.nuxeo.ecm.core.blob.BlobManager;
036import org.nuxeo.ecm.core.model.Session;
037import org.nuxeo.ecm.core.schema.DocumentType;
038import org.nuxeo.ecm.core.schema.SchemaManager;
039import org.nuxeo.ecm.core.schema.TypeConstants;
040import org.nuxeo.ecm.core.schema.types.ComplexType;
041import org.nuxeo.ecm.core.schema.types.CompositeType;
042import org.nuxeo.ecm.core.schema.types.Field;
043import org.nuxeo.ecm.core.schema.types.ListType;
044import org.nuxeo.ecm.core.schema.types.Schema;
045import org.nuxeo.ecm.core.schema.types.Type;
046import org.nuxeo.ecm.core.storage.FulltextConfigurationFactory;
047import org.nuxeo.ecm.core.storage.FulltextDescriptor;
048import org.nuxeo.ecm.core.storage.lock.LockManagerService;
049import org.nuxeo.runtime.api.Framework;
050
051/**
052 * Provides sharing behavior for repository sessions and other basic functions.
053 *
054 * @since 5.9.4
055 */
056public abstract class DBSRepositoryBase implements DBSRepository {
057
058    private static final Log log = LogFactory.getLog(DBSRepositoryBase.class);
059
060    public static final String TYPE_ROOT = "Root";
061
062    // change to have deterministic pseudo-UUID generation for debugging
063    public static final boolean DEBUG_UUIDS = false;
064
065    public static final String UUID_ZERO = "00000000-0000-0000-0000-000000000000";
066
067    public static final String UUID_ZERO_DEBUG = "UUID_0";
068
069    /**
070     * Type of id to used for documents.
071     *
072     * @since 8.3
073     */
074    public enum IdType {
075        /** Random UUID stored in a string. */
076        varchar,
077        /** Random UUID stored as a native UUID type. */
078        uuid,
079        /** Integer sequence maintained by the database. */
080        sequence,
081        /**
082         * Integer sequence maintained by the database, in a pseudo-random order, as hex.
083         *
084         * @since 11.1
085         */
086        sequenceHexRandomized,
087    }
088
089    /** @since 8.3 */
090    protected final IdType idType;
091
092    protected final String repositoryName;
093
094    protected final FulltextConfiguration fulltextConfiguration;
095
096    protected final BlobManager blobManager;
097
098    protected LockManager lockManager;
099
100    protected final boolean changeTokenEnabled;
101
102    /**
103     * @since 7.4 : used to know if the LockManager was provided by this repository or externally
104     */
105    protected boolean selfRegisteredLockManager = false;
106
107    public DBSRepositoryBase(String repositoryName, DBSRepositoryDescriptor descriptor) {
108        this.repositoryName = repositoryName;
109        String idt = descriptor.idType;
110        List<IdType> allowed = getAllowedIdTypes();
111        if (StringUtils.isBlank(idt)) {
112            idt = allowed.get(0).name();
113        }
114        try {
115            idType = IdType.valueOf(idt);
116            if (!allowed.contains(idType)) {
117                throw new IllegalArgumentException("Invalid id type: " + idt);
118            }
119        } catch (IllegalArgumentException e) {
120            throw new NuxeoException("Unknown id type: " + idt + ", allowed: " + allowed);
121        }
122        FulltextDescriptor fulltextDescriptor = descriptor.getFulltextDescriptor();
123        if (fulltextDescriptor.getFulltextDisabled()) {
124            fulltextConfiguration = null;
125        } else {
126            fulltextConfiguration = FulltextConfigurationFactory.make(fulltextDescriptor);
127        }
128        changeTokenEnabled = descriptor.isChangeTokenEnabled();
129        blobManager = Framework.getService(BlobManager.class);
130        initBlobsPaths();
131        initLockManager();
132    }
133
134    /** Gets the allowed id types for this DBS repository. The first one is the default. */
135    public abstract List<IdType> getAllowedIdTypes();
136
137    /** @since 11.1 */
138    public IdType getIdType() {
139        return idType;
140    }
141
142    @Override
143    public void shutdown() {
144        if (selfRegisteredLockManager) {
145            LockManagerService lms = Framework.getService(LockManagerService.class);
146            if (lms != null) {
147                lms.unregisterLockManager(getLockManagerName());
148            }
149        }
150    }
151
152    @Override
153    public String getName() {
154        return repositoryName;
155    }
156
157    @Override
158    public FulltextConfiguration getFulltextConfiguration() {
159        return fulltextConfiguration;
160    }
161
162    protected String getLockManagerName() {
163        // TODO configure in repo descriptor
164        return getName();
165    }
166
167    protected void initLockManager() {
168        String lockManagerName = getLockManagerName();
169        LockManagerService lockManagerService = Framework.getService(LockManagerService.class);
170        lockManager = lockManagerService.getLockManager(lockManagerName);
171        if (lockManager == null) {
172            // no descriptor, use DBS repository intrinsic lock manager
173            lockManager = this;
174            log.info("Repository " + repositoryName + " using own lock manager");
175            lockManagerService.registerLockManager(lockManagerName, lockManager);
176            selfRegisteredLockManager = true;
177        } else {
178            selfRegisteredLockManager = false;
179            log.info("Repository " + repositoryName + " using lock manager " + lockManager);
180        }
181    }
182
183    @Override
184    public LockManager getLockManager() {
185        return lockManager;
186    }
187
188    @Override
189    public Lock getLock(String id) {
190        try (DBSConnection connection = getConnection()) {
191            return connection.getLock(id);
192        }
193    }
194
195    @Override
196    public Lock setLock(String id, Lock lock) {
197        try (DBSConnection connection = getConnection()) {
198            return connection.setLock(id, lock);
199        }
200    }
201
202    @Override
203    public Lock removeLock(String id, String owner) {
204        try (DBSConnection connection = getConnection()) {
205            return connection.removeLock(id, owner);
206        }
207    }
208
209    protected abstract void initBlobsPaths();
210
211    /** Finds the paths for all blobs in all document types. */
212    protected static abstract class BlobFinder {
213
214        protected final Set<String> schemaDone = new HashSet<>();
215
216        protected final Deque<String> path = new ArrayDeque<>();
217
218        public void visit() {
219            SchemaManager schemaManager = Framework.getService(SchemaManager.class);
220            // document types
221            for (DocumentType docType : schemaManager.getDocumentTypes()) {
222                visitSchemas(docType.getSchemas());
223            }
224            // mixins
225            for (CompositeType type : schemaManager.getFacets()) {
226                visitSchemas(type.getSchemas());
227            }
228        }
229
230        protected void visitSchemas(Collection<Schema> schemas) {
231            for (Schema schema : schemas) {
232                if (schemaDone.add(schema.getName())) {
233                    visitComplexType(schema);
234                }
235            }
236        }
237
238        protected void visitComplexType(ComplexType complexType) {
239            if (TypeConstants.isContentType(complexType)) {
240                recordBlobPath();
241                return;
242            }
243            for (Field field : complexType.getFields()) {
244                visitField(field);
245            }
246        }
247
248        /** Records a blob path, stored in the {@link #path} field. */
249        protected abstract void recordBlobPath();
250
251        protected void visitField(Field field) {
252            Type type = field.getType();
253            if (type.isSimpleType()) {
254                // scalar
255                // assume no bare binary exists
256            } else if (type.isComplexType()) {
257                // complex property
258                String name = field.getName().getPrefixedName();
259                path.addLast(name);
260                visitComplexType((ComplexType) type);
261                path.removeLast();
262            } else {
263                // array or list
264                Type fieldType = ((ListType) type).getFieldType();
265                if (fieldType.isSimpleType()) {
266                    // array
267                    // assume no array of bare binaries exist
268                } else {
269                    // complex list
270                    String name = field.getName().getPrefixedName();
271                    path.addLast(name);
272                    visitComplexType((ComplexType) fieldType);
273                    path.removeLast();
274                }
275            }
276        }
277    }
278
279    @Override
280    public BlobManager getBlobManager() {
281        return blobManager;
282    }
283
284    @Override
285    public boolean isFulltextDisabled() {
286        return fulltextConfiguration == null;
287    }
288
289    @Override
290    public boolean isFulltextStoredInBlob() {
291        return fulltextConfiguration != null && fulltextConfiguration.fulltextStoredInBlob;
292    }
293
294    @Override
295    public boolean isFulltextSearchDisabled() {
296        return isFulltextDisabled() || isFulltextStoredInBlob() || fulltextConfiguration.fulltextSearchDisabled;
297    }
298
299    @Override
300    public boolean isChangeTokenEnabled() {
301        return changeTokenEnabled;
302    }
303
304    @Override
305    public Session getSession() {
306        return new DBSSession(this);
307    }
308
309}