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.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.lang.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    @XNode("indexing/fulltext@parser")
343    public void setFulltextParser(String fulltextParser) {
344        fulltextDescriptor.setFulltextParser(fulltextParser);
345    }
346
347    @XNodeList(value = "indexing/fulltext/index", type = ArrayList.class, componentType = FulltextIndexDescriptor.class)
348    public void setFulltextIndexes(List<FulltextIndexDescriptor> fulltextIndexes) {
349        fulltextDescriptor.setFulltextIndexes(fulltextIndexes);
350    }
351
352    @XNodeList(value = "indexing/excludedTypes/type", type = HashSet.class, componentType = String.class)
353    public void setFulltextExcludedTypes(Set<String> fulltextExcludedTypes) {
354        fulltextDescriptor.setFulltextExcludedTypes(fulltextExcludedTypes);
355    }
356
357    @XNodeList(value = "indexing/includedTypes/type", type = HashSet.class, componentType = String.class)
358    public void setFulltextIncludedTypes(Set<String> fulltextIncludedTypes) {
359        fulltextDescriptor.setFulltextIncludedTypes(fulltextIncludedTypes);
360    }
361
362    // compat
363    @XNodeList(value = "indexing/neverPerDocumentFacets/facet", type = HashSet.class, componentType = String.class)
364    public Set<String> neverPerInstanceMixins = new HashSet<>(0);
365
366    @XNode("pathOptimizations@enabled")
367    private Boolean pathOptimizationsEnabled;
368
369    public boolean getPathOptimizationsEnabled() {
370        return defaultTrue(pathOptimizationsEnabled);
371    }
372
373    protected void setPathOptimizationsEnabled(boolean enabled) {
374        pathOptimizationsEnabled = Boolean.valueOf(enabled);
375    }
376
377    /* @since 5.7 */
378    @XNode("pathOptimizations@version")
379    private Integer pathOptimizationsVersion;
380
381    public int getPathOptimizationsVersion() {
382        return pathOptimizationsVersion == null ? DEFAULT_PATH_OPTIM_VERSION : pathOptimizationsVersion.intValue();
383    }
384
385    @XNode("aclOptimizations@enabled")
386    private Boolean aclOptimizationsEnabled;
387
388    public boolean getAclOptimizationsEnabled() {
389        return defaultTrue(aclOptimizationsEnabled);
390    }
391
392    protected void setAclOptimizationsEnabled(boolean enabled) {
393        aclOptimizationsEnabled = Boolean.valueOf(enabled);
394    }
395
396    /* @since 5.4.2 */
397    @XNode("aclOptimizations@readAclMaxSize")
398    private Integer readAclMaxSize;
399
400    public int getReadAclMaxSize() {
401        return readAclMaxSize == null ? DEFAULT_READ_ACL_MAX_SIZE : readAclMaxSize.intValue();
402    }
403
404    @XNode("usersSeparator@key")
405    public String usersSeparatorKey;
406
407    /** @since 9.1 */
408    @XNode("changeTokenEnabled")
409    private Boolean changeTokenEnabled;
410
411    /** @since 9.1 */
412    public boolean isChangeTokenEnabled() {
413        return defaultFalse(changeTokenEnabled);
414    }
415
416    /** @since 9.1 */
417    public void setChangeTokenEnabled(boolean enabled) {
418        this.changeTokenEnabled = Boolean.valueOf(enabled);
419    }
420
421    public RepositoryDescriptor() {
422    }
423
424    /** Copy constructor. */
425    public RepositoryDescriptor(RepositoryDescriptor other) {
426        name = other.name;
427        label = other.label;
428        isDefault = other.isDefault;
429        pool = other.pool == null ? null : new NuxeoConnectionManagerConfiguration(other.pool);
430        backendClass = other.backendClass;
431        clusterInvalidatorClass = other.clusterInvalidatorClass;
432        cachingMapperClass = other.cachingMapperClass;
433        cachingMapperEnabled = other.cachingMapperEnabled;
434        cachingMapperProperties = new HashMap<>(other.cachingMapperProperties);
435        noDDL = other.noDDL;
436        ddlMode = other.ddlMode;
437        sqlInitFiles = new ArrayList<>(other.sqlInitFiles);
438        softDeleteEnabled = other.softDeleteEnabled;
439        proxiesEnabled = other.proxiesEnabled;
440        schemaFields = FieldDescriptor.copyList(other.schemaFields);
441        arrayColumns = other.arrayColumns;
442        childNameUniqueConstraintEnabled = other.childNameUniqueConstraintEnabled;
443        collectionUniqueConstraintEnabled = other.collectionUniqueConstraintEnabled;
444        idType = other.idType;
445        clusterNodeId = other.clusterNodeId;
446        clusteringEnabled = other.clusteringEnabled;
447        clusteringDelay = other.clusteringDelay;
448        fulltextAnalyzer = other.fulltextAnalyzer;
449        fulltextCatalog = other.fulltextCatalog;
450        fulltextDescriptor = new FulltextDescriptor(other.fulltextDescriptor);
451        neverPerInstanceMixins = other.neverPerInstanceMixins;
452        pathOptimizationsEnabled = other.pathOptimizationsEnabled;
453        pathOptimizationsVersion = other.pathOptimizationsVersion;
454        aclOptimizationsEnabled = other.aclOptimizationsEnabled;
455        readAclMaxSize = other.readAclMaxSize;
456        usersSeparatorKey = other.usersSeparatorKey;
457        changeTokenEnabled = other.changeTokenEnabled;
458    }
459
460    public void merge(RepositoryDescriptor other) {
461        if (other.name != null) {
462            name = other.name;
463        }
464        if (other.label != null) {
465            label = other.label;
466        }
467        if (other.isDefault != null) {
468            isDefault = other.isDefault;
469        }
470        if (other.pool != null) {
471            if (pool == null) {
472                pool = new NuxeoConnectionManagerConfiguration(other.pool);
473            } else {
474                pool.merge(other.pool);
475            }
476        }
477        if (other.backendClass != null) {
478            backendClass = other.backendClass;
479        }
480        if (other.clusterInvalidatorClass != null) {
481            clusterInvalidatorClass = other.clusterInvalidatorClass;
482        }
483        if (other.cachingMapperClass != null) {
484            cachingMapperClass = other.cachingMapperClass;
485        }
486        if (other.cachingMapperEnabled != null) {
487            cachingMapperEnabled = other.cachingMapperEnabled;
488        }
489        cachingMapperProperties.putAll(other.cachingMapperProperties);
490        if (other.noDDL != null) {
491            noDDL = other.noDDL;
492        }
493        if (other.ddlMode != null) {
494            ddlMode = other.ddlMode;
495        }
496        sqlInitFiles.addAll(other.sqlInitFiles);
497        if (other.softDeleteEnabled != null) {
498            softDeleteEnabled = other.softDeleteEnabled;
499        }
500        if (other.proxiesEnabled != null) {
501            proxiesEnabled = other.proxiesEnabled;
502        }
503        if (other.idType != null) {
504            idType = other.idType;
505        }
506        if (other.clusterNodeId != null) {
507            clusterNodeId = other.clusterNodeId;
508        }
509        if (other.clusteringEnabled != null) {
510            clusteringEnabled = other.clusteringEnabled;
511        }
512        if (other.clusteringDelay != null) {
513            clusteringDelay = other.clusteringDelay;
514        }
515        for (FieldDescriptor of : other.schemaFields) {
516            boolean append = true;
517            for (FieldDescriptor f : schemaFields) {
518                if (f.field.equals(of.field)) {
519                    f.merge(of);
520                    append = false;
521                    break;
522                }
523            }
524            if (append) {
525                schemaFields.add(of);
526            }
527        }
528        if (other.arrayColumns != null) {
529            arrayColumns = other.arrayColumns;
530        }
531        if (other.childNameUniqueConstraintEnabled != null) {
532            childNameUniqueConstraintEnabled = other.childNameUniqueConstraintEnabled;
533        }
534        if (other.collectionUniqueConstraintEnabled != null) {
535            collectionUniqueConstraintEnabled = other.collectionUniqueConstraintEnabled;
536        }
537        if (other.fulltextAnalyzer != null) {
538            fulltextAnalyzer = other.fulltextAnalyzer;
539        }
540        if (other.fulltextCatalog != null) {
541            fulltextCatalog = other.fulltextCatalog;
542        }
543        fulltextDescriptor.merge(other.fulltextDescriptor);
544        neverPerInstanceMixins.addAll(other.neverPerInstanceMixins);
545        if (other.pathOptimizationsEnabled != null) {
546            pathOptimizationsEnabled = other.pathOptimizationsEnabled;
547        }
548        if (other.pathOptimizationsVersion != null) {
549            pathOptimizationsVersion = other.pathOptimizationsVersion;
550        }
551        if (other.aclOptimizationsEnabled != null) {
552            aclOptimizationsEnabled = other.aclOptimizationsEnabled;
553        }
554        if (other.readAclMaxSize != null) {
555            readAclMaxSize = other.readAclMaxSize;
556        }
557        if (other.usersSeparatorKey != null) {
558            usersSeparatorKey = other.usersSeparatorKey;
559        }
560        if (other.changeTokenEnabled != null) {
561            changeTokenEnabled = other.changeTokenEnabled;
562        }
563    }
564
565}