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