001/*
002 * (C) Copyright 2006-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.sql;
020
021import java.util.ArrayList;
022import java.util.HashMap;
023import java.util.HashSet;
024import java.util.List;
025import java.util.Map;
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.common.xmap.annotation.XNode;
032import org.nuxeo.common.xmap.annotation.XNodeList;
033import org.nuxeo.common.xmap.annotation.XNodeMap;
034import org.nuxeo.common.xmap.annotation.XObject;
035import org.nuxeo.ecm.core.api.repository.PoolConfiguration;
036import org.nuxeo.ecm.core.storage.FulltextDescriptor;
037import org.nuxeo.ecm.core.storage.FulltextDescriptor.FulltextIndexDescriptor;
038
039/**
040 * Low-level VCS Repository Descriptor.
041 */
042@XObject(value = "repository", order = { "@name" })
043public class RepositoryDescriptor {
044
045    private static final Log log = LogFactory.getLog(RepositoryDescriptor.class);
046
047    /** @deprecated since 11.1, was PostgreSQL-specific */
048    @Deprecated
049    public static final int DEFAULT_READ_ACL_MAX_SIZE = 4096;
050
051    public static final int DEFAULT_PATH_OPTIM_VERSION = 2;
052
053    /** At startup, DDL changes are not detected. */
054    public static final String DDL_MODE_IGNORE = "ignore";
055
056    /** At startup, DDL changes are detected and if not empty they are dumped. */
057    public static final String DDL_MODE_DUMP = "dump";
058
059    /** At startup, DDL changes are detected and executed. */
060    public static final String DDL_MODE_EXECUTE = "execute";
061
062    /** At startup, DDL changes are detected and if not empty Nuxeo startup is aborted. */
063    public static final String DDL_MODE_ABORT = "abort";
064
065    /** Specifies that stored procedure detection must be compatible with previous Nuxeo versions. */
066    public static final String DDL_MODE_COMPAT = "compat";
067
068    @XObject(value = "field")
069    public static class FieldDescriptor {
070
071        // empty constructor needed by XMap
072        public FieldDescriptor() {
073        }
074
075        /** Copy constructor. */
076        public FieldDescriptor(FieldDescriptor other) {
077            type = other.type;
078            field = other.field;
079            table = other.table;
080            column = other.column;
081        }
082
083        public static List<FieldDescriptor> copyList(List<FieldDescriptor> other) {
084            List<FieldDescriptor> copy = new ArrayList<>(other.size());
085            for (FieldDescriptor fd : other) {
086                copy.add(new FieldDescriptor(fd));
087            }
088            return copy;
089        }
090
091        public void merge(FieldDescriptor other) {
092            if (other.field != null) {
093                field = other.field;
094            }
095            if (other.type != null) {
096                type = other.type;
097            }
098            if (other.table != null) {
099                table = other.table;
100            }
101            if (other.column != null) {
102                column = other.column;
103            }
104        }
105
106        @XNode("@type")
107        public String type;
108
109        public String field;
110
111        @XNode("@name")
112        public void setName(String name) {
113            if (!StringUtils.isBlank(name) && field == null) {
114                field = name;
115            }
116        }
117
118        // compat with older syntax
119        @XNode
120        public void setXNodeContent(String name) {
121            setName(name);
122        }
123
124        @XNode("@table")
125        public String table;
126
127        @XNode("@column")
128        public String column;
129
130        @Override
131        public String toString() {
132            return this.getClass().getSimpleName() + '(' + field + ",type=" + type + ",table=" + table + ",column="
133                    + column + ")";
134        }
135    }
136
137    /** False if the boolean is null or FALSE, true otherwise. */
138    private static boolean defaultFalse(Boolean bool) {
139        return Boolean.TRUE.equals(bool);
140    }
141
142    /** True if the boolean is null or TRUE, false otherwise. */
143    private static boolean defaultTrue(Boolean bool) {
144        return !Boolean.FALSE.equals(bool);
145    }
146
147    public String name;
148
149    @XNode("@name")
150    public void setName(String name) {
151        this.name = name;
152    }
153
154    @XNode("@label")
155    public String label;
156
157    @XNode("@isDefault")
158    private Boolean isDefault;
159
160    public Boolean isDefault() {
161        return isDefault;
162    }
163
164    @XNode("@headless")
165    private Boolean headless;
166
167    /** @since 11.2 */
168    public Boolean isHeadless() {
169        return headless;
170    }
171
172    // compat, when used with old-style extension point syntax
173    // and nested repository
174    @XNode("repository")
175    public RepositoryDescriptor repositoryDescriptor;
176
177    @XNode("pool")
178    public PoolConfiguration pool;
179
180    @XNode("clusterInvalidatorClass")
181    public Class<? extends VCSClusterInvalidator> clusterInvalidatorClass;
182
183    @XNode("cachingMapper@class")
184    public Class<? extends CachingMapper> cachingMapperClass;
185
186    @XNode("cachingMapper@enabled")
187    private Boolean cachingMapperEnabled;
188
189    public boolean getCachingMapperEnabled() {
190        return defaultTrue(cachingMapperEnabled);
191    }
192
193    @XNodeMap(value = "cachingMapper/property", key = "@name", type = HashMap.class, componentType = String.class)
194    public Map<String, String> cachingMapperProperties = new HashMap<>();
195
196    @XNode("ddlMode")
197    private String ddlMode;
198
199    public String getDDLMode() {
200        return ddlMode;
201    }
202
203    @XNode("noDDL")
204    private Boolean noDDL;
205
206    public boolean getNoDDL() {
207        return defaultFalse(noDDL);
208    }
209
210    @XNodeList(value = "sqlInitFile", type = ArrayList.class, componentType = String.class)
211    public List<String> sqlInitFiles = new ArrayList<>(0);
212
213    @XNode("softDelete@enabled")
214    private Boolean softDeleteEnabled;
215
216    public boolean getSoftDeleteEnabled() {
217        return defaultFalse(softDeleteEnabled);
218    }
219
220    protected void setSoftDeleteEnabled(boolean enabled) {
221        softDeleteEnabled = Boolean.valueOf(enabled);
222    }
223
224    @XNode("proxies@enabled")
225    private Boolean proxiesEnabled;
226
227    public boolean getProxiesEnabled() {
228        return defaultTrue(proxiesEnabled);
229    }
230
231    protected void setProxiesEnabled(boolean enabled) {
232        proxiesEnabled = Boolean.valueOf(enabled);
233    }
234
235    @XNode("idType")
236    public String idType; // "varchar", "uuid", "sequence"
237
238    @XNodeList(value = "schema/field", type = ArrayList.class, componentType = FieldDescriptor.class)
239    public List<FieldDescriptor> schemaFields = new ArrayList<>(0);
240
241    @XNode("schema/arrayColumns")
242    private Boolean arrayColumns;
243
244    public boolean getArrayColumns() {
245        return defaultFalse(arrayColumns);
246    }
247
248    public void setArrayColumns(boolean enabled) {
249        arrayColumns = Boolean.valueOf(enabled);
250    }
251
252    @XNode("childNameUniqueConstraintEnabled")
253    private Boolean childNameUniqueConstraintEnabled;
254
255    public boolean getChildNameUniqueConstraintEnabled() {
256        return defaultTrue(childNameUniqueConstraintEnabled);
257    }
258
259    @XNode("collectionUniqueConstraintEnabled")
260    private Boolean collectionUniqueConstraintEnabled;
261
262    public boolean getCollectionUniqueConstraintEnabled() {
263        return defaultTrue(collectionUniqueConstraintEnabled);
264    }
265
266    @XNode("indexing/queryMaker@class")
267    public void setQueryMakerDeprecated(String klass) {
268        log.warn("Setting queryMaker from repository configuration is now deprecated");
269    }
270
271    // VCS-specific fulltext indexing options
272    private String fulltextAnalyzer;
273
274    public String getFulltextAnalyzer() {
275        return fulltextAnalyzer;
276    }
277
278    @XNode("indexing/fulltext@analyzer")
279    public void setFulltextAnalyzer(String fulltextAnalyzer) {
280        this.fulltextAnalyzer = fulltextAnalyzer;
281    }
282
283    private String fulltextCatalog;
284
285    public String getFulltextCatalog() {
286        return fulltextCatalog;
287    }
288
289    @XNode("indexing/fulltext@catalog")
290    public void setFulltextCatalog(String fulltextCatalog) {
291        this.fulltextCatalog = fulltextCatalog;
292    }
293
294    private FulltextDescriptor fulltextDescriptor = new FulltextDescriptor();
295
296    public FulltextDescriptor getFulltextDescriptor() {
297        return fulltextDescriptor;
298    }
299
300    @XNode("indexing/fulltext@fieldSizeLimit")
301    public void setFulltextFieldSizeLimit(int fieldSizeLimit) {
302        fulltextDescriptor.setFulltextFieldSizeLimit(fieldSizeLimit);
303    }
304
305    @XNode("indexing/fulltext@disabled")
306    public void setFulltextDisabled(boolean disabled) {
307        fulltextDescriptor.setFulltextDisabled(disabled);
308    }
309
310    /** @since 11.1 */
311    @XNode("indexing/fulltext@storedInBlob")
312    public void setFulltextStoredInBlob(boolean storedInBlob) {
313        fulltextDescriptor.setFulltextStoredInBlob(storedInBlob);
314    }
315
316    @XNode("indexing/fulltext@searchDisabled")
317    public void setFulltextSearchDisabled(boolean disabled) {
318        fulltextDescriptor.setFulltextSearchDisabled(disabled);
319    }
320
321    @XNodeList(value = "indexing/fulltext/index", type = ArrayList.class, componentType = FulltextIndexDescriptor.class)
322    public void setFulltextIndexes(List<FulltextIndexDescriptor> fulltextIndexes) {
323        fulltextDescriptor.setFulltextIndexes(fulltextIndexes);
324    }
325
326    @XNodeList(value = "indexing/excludedTypes/type", type = HashSet.class, componentType = String.class)
327    public void setFulltextExcludedTypes(Set<String> fulltextExcludedTypes) {
328        fulltextDescriptor.setFulltextExcludedTypes(fulltextExcludedTypes);
329    }
330
331    @XNodeList(value = "indexing/includedTypes/type", type = HashSet.class, componentType = String.class)
332    public void setFulltextIncludedTypes(Set<String> fulltextIncludedTypes) {
333        fulltextDescriptor.setFulltextIncludedTypes(fulltextIncludedTypes);
334    }
335
336    // compat
337    @XNodeList(value = "indexing/neverPerDocumentFacets/facet", type = HashSet.class, componentType = String.class)
338    public Set<String> neverPerInstanceMixins = new HashSet<>(0);
339
340    @XNode("pathOptimizations@enabled")
341    private Boolean pathOptimizationsEnabled;
342
343    public boolean getPathOptimizationsEnabled() {
344        return defaultTrue(pathOptimizationsEnabled);
345    }
346
347    protected void setPathOptimizationsEnabled(boolean enabled) {
348        pathOptimizationsEnabled = Boolean.valueOf(enabled);
349    }
350
351    /* @since 5.7 */
352    @XNode("pathOptimizations@version")
353    private Integer pathOptimizationsVersion;
354
355    public int getPathOptimizationsVersion() {
356        return pathOptimizationsVersion == null ? DEFAULT_PATH_OPTIM_VERSION : pathOptimizationsVersion.intValue();
357    }
358
359    @XNode("aclOptimizations@enabled")
360    private Boolean aclOptimizationsEnabled;
361
362    public boolean getAclOptimizationsEnabled() {
363        return defaultTrue(aclOptimizationsEnabled);
364    }
365
366    protected void setAclOptimizationsEnabled(boolean enabled) {
367        aclOptimizationsEnabled = Boolean.valueOf(enabled);
368    }
369
370    /* @since 5.4.2 */
371    @XNode("aclOptimizations@readAclMaxSize")
372    private Integer readAclMaxSize;
373
374    public int getReadAclMaxSize() {
375        return readAclMaxSize == null ? 0 : readAclMaxSize.intValue();
376    }
377
378    @XNode("usersSeparator@key")
379    public String usersSeparatorKey;
380
381    /** @since 9.1 */
382    @XNode("changeTokenEnabled")
383    private Boolean changeTokenEnabled;
384
385    /** @since 9.1 */
386    public boolean isChangeTokenEnabled() {
387        return defaultFalse(changeTokenEnabled);
388    }
389
390    /** @since 9.1 */
391    public void setChangeTokenEnabled(boolean enabled) {
392        this.changeTokenEnabled = Boolean.valueOf(enabled);
393    }
394
395    public RepositoryDescriptor() {
396    }
397
398    /** Copy constructor. */
399    public RepositoryDescriptor(RepositoryDescriptor other) {
400        name = other.name;
401        label = other.label;
402        isDefault = other.isDefault;
403        headless = other.headless;
404        pool = other.pool == null ? null : new PoolConfiguration(other.pool);
405        clusterInvalidatorClass = other.clusterInvalidatorClass;
406        cachingMapperClass = other.cachingMapperClass;
407        cachingMapperEnabled = other.cachingMapperEnabled;
408        cachingMapperProperties = new HashMap<>(other.cachingMapperProperties);
409        noDDL = other.noDDL;
410        ddlMode = other.ddlMode;
411        sqlInitFiles = new ArrayList<>(other.sqlInitFiles);
412        softDeleteEnabled = other.softDeleteEnabled;
413        proxiesEnabled = other.proxiesEnabled;
414        schemaFields = FieldDescriptor.copyList(other.schemaFields);
415        arrayColumns = other.arrayColumns;
416        childNameUniqueConstraintEnabled = other.childNameUniqueConstraintEnabled;
417        collectionUniqueConstraintEnabled = other.collectionUniqueConstraintEnabled;
418        idType = other.idType;
419        fulltextAnalyzer = other.fulltextAnalyzer;
420        fulltextCatalog = other.fulltextCatalog;
421        fulltextDescriptor = new FulltextDescriptor(other.fulltextDescriptor);
422        neverPerInstanceMixins = other.neverPerInstanceMixins;
423        pathOptimizationsEnabled = other.pathOptimizationsEnabled;
424        pathOptimizationsVersion = other.pathOptimizationsVersion;
425        aclOptimizationsEnabled = other.aclOptimizationsEnabled;
426        readAclMaxSize = other.readAclMaxSize;
427        usersSeparatorKey = other.usersSeparatorKey;
428        changeTokenEnabled = other.changeTokenEnabled;
429    }
430
431    public void merge(RepositoryDescriptor other) {
432        if (other.name != null) {
433            name = other.name;
434        }
435        if (other.label != null) {
436            label = other.label;
437        }
438        if (other.isDefault != null) {
439            isDefault = other.isDefault;
440        }
441        if (other.headless != null) {
442            headless = other.headless;
443        }
444        if (other.pool != null) {
445            if (pool == null) {
446                pool = new PoolConfiguration(other.pool);
447            } else {
448                pool.merge(other.pool);
449            }
450        }
451        if (other.clusterInvalidatorClass != null) {
452            clusterInvalidatorClass = other.clusterInvalidatorClass;
453        }
454        if (other.cachingMapperClass != null) {
455            cachingMapperClass = other.cachingMapperClass;
456        }
457        if (other.cachingMapperEnabled != null) {
458            cachingMapperEnabled = other.cachingMapperEnabled;
459        }
460        cachingMapperProperties.putAll(other.cachingMapperProperties);
461        if (other.noDDL != null) {
462            noDDL = other.noDDL;
463        }
464        if (other.ddlMode != null) {
465            ddlMode = other.ddlMode;
466        }
467        sqlInitFiles.addAll(other.sqlInitFiles);
468        if (other.softDeleteEnabled != null) {
469            softDeleteEnabled = other.softDeleteEnabled;
470        }
471        if (other.proxiesEnabled != null) {
472            proxiesEnabled = other.proxiesEnabled;
473        }
474        if (other.idType != null) {
475            idType = other.idType;
476        }
477        for (FieldDescriptor of : other.schemaFields) {
478            boolean append = true;
479            for (FieldDescriptor f : schemaFields) {
480                if (f.field.equals(of.field)) {
481                    f.merge(of);
482                    append = false;
483                    break;
484                }
485            }
486            if (append) {
487                schemaFields.add(of);
488            }
489        }
490        if (other.arrayColumns != null) {
491            arrayColumns = other.arrayColumns;
492        }
493        if (other.childNameUniqueConstraintEnabled != null) {
494            childNameUniqueConstraintEnabled = other.childNameUniqueConstraintEnabled;
495        }
496        if (other.collectionUniqueConstraintEnabled != null) {
497            collectionUniqueConstraintEnabled = other.collectionUniqueConstraintEnabled;
498        }
499        if (other.fulltextAnalyzer != null) {
500            fulltextAnalyzer = other.fulltextAnalyzer;
501        }
502        if (other.fulltextCatalog != null) {
503            fulltextCatalog = other.fulltextCatalog;
504        }
505        fulltextDescriptor.merge(other.fulltextDescriptor);
506        neverPerInstanceMixins.addAll(other.neverPerInstanceMixins);
507        if (other.pathOptimizationsEnabled != null) {
508            pathOptimizationsEnabled = other.pathOptimizationsEnabled;
509        }
510        if (other.pathOptimizationsVersion != null) {
511            pathOptimizationsVersion = other.pathOptimizationsVersion;
512        }
513        if (other.aclOptimizationsEnabled != null) {
514            aclOptimizationsEnabled = other.aclOptimizationsEnabled;
515        }
516        if (other.readAclMaxSize != null) {
517            readAclMaxSize = other.readAclMaxSize;
518        }
519        if (other.usersSeparatorKey != null) {
520            usersSeparatorKey = other.usersSeparatorKey;
521        }
522        if (other.changeTokenEnabled != null) {
523            changeTokenEnabled = other.changeTokenEnabled;
524        }
525    }
526
527}