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("indexing/queryMaker@class")
287    public void setQueryMakerDeprecated(String klass) {
288        log.warn("Setting queryMaker from repository configuration is now deprecated");
289    }
290
291    // VCS-specific fulltext indexing options
292    private String fulltextAnalyzer;
293
294    public String getFulltextAnalyzer() {
295        return fulltextAnalyzer;
296    }
297
298    @XNode("indexing/fulltext@analyzer")
299    public void setFulltextAnalyzer(String fulltextAnalyzer) {
300        this.fulltextAnalyzer = fulltextAnalyzer;
301    }
302
303    private String fulltextCatalog;
304
305    public String getFulltextCatalog() {
306        return fulltextCatalog;
307    }
308
309    @XNode("indexing/fulltext@catalog")
310    public void setFulltextCatalog(String fulltextCatalog) {
311        this.fulltextCatalog = fulltextCatalog;
312    }
313
314    private FulltextDescriptor fulltextDescriptor = new FulltextDescriptor();
315
316    public FulltextDescriptor getFulltextDescriptor() {
317        return fulltextDescriptor;
318    }
319
320    @XNode("indexing/fulltext@fieldSizeLimit")
321    public void setFulltextFieldSizeLimit(int fieldSizeLimit) {
322        fulltextDescriptor.setFulltextFieldSizeLimit(fieldSizeLimit);
323    }
324
325    @XNode("indexing/fulltext@disabled")
326    public void setFulltextDisabled(boolean disabled) {
327        fulltextDescriptor.setFulltextDisabled(disabled);
328    }
329
330    @XNode("indexing/fulltext@searchDisabled")
331    public void setFulltextSearchDisabled(boolean disabled) {
332        fulltextDescriptor.setFulltextSearchDisabled(disabled);
333    }
334
335    @XNode("indexing/fulltext@parser")
336    public void setFulltextParser(String fulltextParser) {
337        fulltextDescriptor.setFulltextParser(fulltextParser);
338    }
339
340    @XNodeList(value = "indexing/fulltext/index", type = ArrayList.class, componentType = FulltextIndexDescriptor.class)
341    public void setFulltextIndexes(List<FulltextIndexDescriptor> fulltextIndexes) {
342        fulltextDescriptor.setFulltextIndexes(fulltextIndexes);
343    }
344
345    @XNodeList(value = "indexing/excludedTypes/type", type = HashSet.class, componentType = String.class)
346    public void setFulltextExcludedTypes(Set<String> fulltextExcludedTypes) {
347        fulltextDescriptor.setFulltextExcludedTypes(fulltextExcludedTypes);
348    }
349
350    @XNodeList(value = "indexing/includedTypes/type", type = HashSet.class, componentType = String.class)
351    public void setFulltextIncludedTypes(Set<String> fulltextIncludedTypes) {
352        fulltextDescriptor.setFulltextIncludedTypes(fulltextIncludedTypes);
353    }
354
355    // compat
356    @XNodeList(value = "indexing/neverPerDocumentFacets/facet", type = HashSet.class, componentType = String.class)
357    public Set<String> neverPerInstanceMixins = new HashSet<>(0);
358
359    @XNode("pathOptimizations@enabled")
360    private Boolean pathOptimizationsEnabled;
361
362    public boolean getPathOptimizationsEnabled() {
363        return defaultTrue(pathOptimizationsEnabled);
364    }
365
366    protected void setPathOptimizationsEnabled(boolean enabled) {
367        pathOptimizationsEnabled = Boolean.valueOf(enabled);
368    }
369
370    /* @since 5.7 */
371    @XNode("pathOptimizations@version")
372    private Integer pathOptimizationsVersion;
373
374    public int getPathOptimizationsVersion() {
375        return pathOptimizationsVersion == null ? DEFAULT_PATH_OPTIM_VERSION : pathOptimizationsVersion.intValue();
376    }
377
378    @XNode("aclOptimizations@enabled")
379    private Boolean aclOptimizationsEnabled;
380
381    public boolean getAclOptimizationsEnabled() {
382        return defaultTrue(aclOptimizationsEnabled);
383    }
384
385    protected void setAclOptimizationsEnabled(boolean enabled) {
386        aclOptimizationsEnabled = Boolean.valueOf(enabled);
387    }
388
389    /* @since 5.4.2 */
390    @XNode("aclOptimizations@readAclMaxSize")
391    private Integer readAclMaxSize;
392
393    public int getReadAclMaxSize() {
394        return readAclMaxSize == null ? DEFAULT_READ_ACL_MAX_SIZE : readAclMaxSize.intValue();
395    }
396
397    @XNode("usersSeparator@key")
398    public String usersSeparatorKey;
399
400    /** @since 9.1 */
401    @XNode("changeTokenEnabled")
402    private Boolean changeTokenEnabled;
403
404    /** @since 9.1 */
405    public boolean isChangeTokenEnabled() {
406        return defaultFalse(changeTokenEnabled);
407    }
408
409    /** @since 9.1 */
410    public void setChangeTokenEnabled(boolean enabled) {
411        this.changeTokenEnabled = Boolean.valueOf(enabled);
412    }
413
414    public RepositoryDescriptor() {
415    }
416
417    /** Copy constructor. */
418    public RepositoryDescriptor(RepositoryDescriptor other) {
419        name = other.name;
420        label = other.label;
421        isDefault = other.isDefault;
422        pool = other.pool == null ? null : new NuxeoConnectionManagerConfiguration(other.pool);
423        backendClass = other.backendClass;
424        clusterInvalidatorClass = other.clusterInvalidatorClass;
425        cachingMapperClass = other.cachingMapperClass;
426        cachingMapperEnabled = other.cachingMapperEnabled;
427        cachingMapperProperties = new HashMap<>(other.cachingMapperProperties);
428        noDDL = other.noDDL;
429        ddlMode = other.ddlMode;
430        sqlInitFiles = new ArrayList<>(other.sqlInitFiles);
431        softDeleteEnabled = other.softDeleteEnabled;
432        proxiesEnabled = other.proxiesEnabled;
433        schemaFields = FieldDescriptor.copyList(other.schemaFields);
434        arrayColumns = other.arrayColumns;
435        idType = other.idType;
436        clusterNodeId = other.clusterNodeId;
437        clusteringEnabled = other.clusteringEnabled;
438        clusteringDelay = other.clusteringDelay;
439        fulltextAnalyzer = other.fulltextAnalyzer;
440        fulltextCatalog = other.fulltextCatalog;
441        fulltextDescriptor = new FulltextDescriptor(other.fulltextDescriptor);
442        neverPerInstanceMixins = other.neverPerInstanceMixins;
443        pathOptimizationsEnabled = other.pathOptimizationsEnabled;
444        pathOptimizationsVersion = other.pathOptimizationsVersion;
445        aclOptimizationsEnabled = other.aclOptimizationsEnabled;
446        readAclMaxSize = other.readAclMaxSize;
447        usersSeparatorKey = other.usersSeparatorKey;
448        changeTokenEnabled = other.changeTokenEnabled;
449    }
450
451    public void merge(RepositoryDescriptor other) {
452        if (other.name != null) {
453            name = other.name;
454        }
455        if (other.label != null) {
456            label = other.label;
457        }
458        if (other.isDefault != null) {
459            isDefault = other.isDefault;
460        }
461        if (other.pool != null) {
462            if (pool == null) {
463                pool = new NuxeoConnectionManagerConfiguration(other.pool);
464            } else {
465                pool.merge(other.pool);
466            }
467        }
468        if (other.backendClass != null) {
469            backendClass = other.backendClass;
470        }
471        if (other.clusterInvalidatorClass != null) {
472            clusterInvalidatorClass = other.clusterInvalidatorClass;
473        }
474        if (other.cachingMapperClass != null) {
475            cachingMapperClass = other.cachingMapperClass;
476        }
477        if (other.cachingMapperEnabled != null) {
478            cachingMapperEnabled = other.cachingMapperEnabled;
479        }
480        cachingMapperProperties.putAll(other.cachingMapperProperties);
481        if (other.noDDL != null) {
482            noDDL = other.noDDL;
483        }
484        if (other.ddlMode != null) {
485            ddlMode = other.ddlMode;
486        }
487        sqlInitFiles.addAll(other.sqlInitFiles);
488        if (other.softDeleteEnabled != null) {
489            softDeleteEnabled = other.softDeleteEnabled;
490        }
491        if (other.proxiesEnabled != null) {
492            proxiesEnabled = other.proxiesEnabled;
493        }
494        if (other.idType != null) {
495            idType = other.idType;
496        }
497        if (other.clusterNodeId != null) {
498            clusterNodeId = other.clusterNodeId;
499        }
500        if (other.clusteringEnabled != null) {
501            clusteringEnabled = other.clusteringEnabled;
502        }
503        if (other.clusteringDelay != null) {
504            clusteringDelay = other.clusteringDelay;
505        }
506        for (FieldDescriptor of : other.schemaFields) {
507            boolean append = true;
508            for (FieldDescriptor f : schemaFields) {
509                if (f.field.equals(of.field)) {
510                    f.merge(of);
511                    append = false;
512                    break;
513                }
514            }
515            if (append) {
516                schemaFields.add(of);
517            }
518        }
519        if (other.arrayColumns != null) {
520            arrayColumns = other.arrayColumns;
521        }
522        if (other.fulltextAnalyzer != null) {
523            fulltextAnalyzer = other.fulltextAnalyzer;
524        }
525        if (other.fulltextCatalog != null) {
526            fulltextCatalog = other.fulltextCatalog;
527        }
528        fulltextDescriptor.merge(other.fulltextDescriptor);
529        neverPerInstanceMixins.addAll(other.neverPerInstanceMixins);
530        if (other.pathOptimizationsEnabled != null) {
531            pathOptimizationsEnabled = other.pathOptimizationsEnabled;
532        }
533        if (other.pathOptimizationsVersion != null) {
534            pathOptimizationsVersion = other.pathOptimizationsVersion;
535        }
536        if (other.aclOptimizationsEnabled != null) {
537            aclOptimizationsEnabled = other.aclOptimizationsEnabled;
538        }
539        if (other.readAclMaxSize != null) {
540            readAclMaxSize = other.readAclMaxSize;
541        }
542        if (other.usersSeparatorKey != null) {
543            usersSeparatorKey = other.usersSeparatorKey;
544        }
545        if (other.changeTokenEnabled != null) {
546            changeTokenEnabled = other.changeTokenEnabled;
547        }
548    }
549
550}