001/*
002 * Copyright (c) 2006-2015 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the Eclipse Public License v1.0
006 * which accompanies this distribution, and is available at
007 * http://www.eclipse.org/legal/epl-v10.html
008 *
009 * Contributors:
010 *     Florent Guillaume
011 */
012package org.nuxeo.ecm.core.storage.sql;
013
014import java.util.ArrayList;
015import java.util.HashMap;
016import java.util.HashSet;
017import java.util.List;
018import java.util.Map;
019import java.util.Set;
020
021import org.apache.commons.lang.ObjectUtils;
022import org.apache.commons.lang.StringUtils;
023import org.apache.commons.logging.Log;
024import org.apache.commons.logging.LogFactory;
025import org.nuxeo.common.xmap.annotation.XNode;
026import org.nuxeo.common.xmap.annotation.XNodeList;
027import org.nuxeo.common.xmap.annotation.XNodeMap;
028import org.nuxeo.common.xmap.annotation.XObject;
029import org.nuxeo.ecm.core.repository.RepositoryFactory;
030import org.nuxeo.runtime.jtajca.NuxeoConnectionManagerConfiguration;
031
032/**
033 * Low-level VCS Repository Descriptor.
034 */
035@XObject(value = "repository")
036public class RepositoryDescriptor {
037
038    private static final Log log = LogFactory.getLog(RepositoryDescriptor.class);
039
040    public static final int DEFAULT_READ_ACL_MAX_SIZE = 4096;
041
042    public static final int DEFAULT_PATH_OPTIM_VERSION = 2;
043
044    @XObject(value = "index")
045    public static class FulltextIndexDescriptor {
046
047        @XNode("@name")
048        public String name;
049
050        @XNode("@analyzer")
051        public String analyzer;
052
053        @XNode("@catalog")
054        public String catalog;
055
056        /** string or blob */
057        @XNode("fieldType")
058        public String fieldType;
059
060        @XNodeList(value = "field", type = HashSet.class, componentType = String.class)
061        public Set<String> fields = new HashSet<>(0);
062
063        @XNodeList(value = "excludeField", type = HashSet.class, componentType = String.class)
064        public Set<String> excludeFields = new HashSet<>(0);
065
066        public FulltextIndexDescriptor() {
067        }
068
069        /** Copy constructor. */
070        public FulltextIndexDescriptor(FulltextIndexDescriptor other) {
071            name = other.name;
072            analyzer = other.analyzer;
073            catalog = other.catalog;
074            fieldType = other.fieldType;
075            fields = new HashSet<>(other.fields);
076            excludeFields = new HashSet<>(other.excludeFields);
077        }
078
079        public static List<FulltextIndexDescriptor> copyList(List<FulltextIndexDescriptor> other) {
080            List<FulltextIndexDescriptor> copy = new ArrayList<>(other.size());
081            for (FulltextIndexDescriptor fid : other) {
082                copy.add(new FulltextIndexDescriptor(fid));
083            }
084            return copy;
085        }
086
087        public void merge(FulltextIndexDescriptor other) {
088            if (other.name != null) {
089                name = other.name;
090            }
091            if (other.analyzer != null) {
092                analyzer = other.analyzer;
093            }
094            if (other.catalog != null) {
095                catalog = other.catalog;
096            }
097            if (other.fieldType != null) {
098                fieldType = other.fieldType;
099            }
100            fields.addAll(other.fields);
101            excludeFields.addAll(other.excludeFields);
102        }
103    }
104
105    @XObject(value = "field")
106    public static class FieldDescriptor {
107
108        // empty constructor needed by XMap
109        public FieldDescriptor() {
110        }
111
112        /** Copy constructor. */
113        public FieldDescriptor(FieldDescriptor other) {
114            type = other.type;
115            field = other.field;
116            table = other.table;
117            column = other.column;
118        }
119
120        public static List<FieldDescriptor> copyList(List<FieldDescriptor> other) {
121            List<FieldDescriptor> copy = new ArrayList<>(other.size());
122            for (FieldDescriptor fd : other) {
123                copy.add(new FieldDescriptor(fd));
124            }
125            return copy;
126        }
127
128        public void merge(FieldDescriptor other) {
129            if (other.field != null) {
130                field = other.field;
131            }
132            if (other.type != null) {
133                type = other.type;
134            }
135            if (other.table != null) {
136                table = other.table;
137            }
138            if (other.column != null) {
139                column = other.column;
140            }
141        }
142
143        @XNode("@type")
144        public String type;
145
146        public String field;
147
148        @XNode("@name")
149        public void setName(String name) {
150            if (!StringUtils.isBlank(name) && field == null) {
151                field = name;
152            }
153        }
154
155        // compat with older syntax
156        @XNode
157        public void setXNodeContent(String name) {
158            setName(name);
159        }
160
161        @XNode("@table")
162        public String table;
163
164        @XNode("@column")
165        public String column;
166
167        @Override
168        public String toString() {
169            return this.getClass().getSimpleName() + '(' + field + ",type=" + type + ",table=" + table + ",column="
170                    + column + ")";
171        }
172    }
173
174    /** False if the boolean is null or FALSE, true otherwise. */
175    private static boolean defaultFalse(Boolean bool) {
176        return Boolean.TRUE.equals(bool);
177    }
178
179    /** True if the boolean is null or TRUE, false otherwise. */
180    private static boolean defaultTrue(Boolean bool) {
181        return !Boolean.FALSE.equals(bool);
182    }
183
184    public String name;
185
186    @XNode("@name")
187    public void setName(String name) {
188        this.name = name;
189        pool.setName("repository/" + name);
190    }
191
192    @XNode("@label")
193    public String label;
194
195    @XNode("@isDefault")
196    private Boolean isDefault;
197
198    public Boolean isDefault() {
199        return isDefault;
200    }
201
202    @XNode("@factory")
203    private Class<? extends RepositoryFactory> repositoryFactoryClass;
204
205    public Class<? extends RepositoryFactory> getRepositoryFactoryClass() {
206        return repositoryFactoryClass;
207    }
208
209    public void setRepositoryFactoryClass(Class<? extends RepositoryFactory> klass) {
210        repositoryFactoryClass = klass;
211    }
212
213    // compat, when used with old-style extension point syntax
214    // and nested repository
215    @XNode("repository")
216    public RepositoryDescriptor repositoryDescriptor;
217
218    public NuxeoConnectionManagerConfiguration pool = new NuxeoConnectionManagerConfiguration();
219
220    @XNode("pool")
221    public void setPool(NuxeoConnectionManagerConfiguration pool) {
222        pool.setName("repository/" + name);
223        this.pool = pool;
224    }
225
226    @XNode("backendClass")
227    public Class<? extends RepositoryBackend> backendClass;
228
229    @XNode("clusterInvalidatorClass")
230    public Class<? extends ClusterInvalidator> clusterInvalidatorClass;
231
232    @XNode("cachingMapper@class")
233    public Class<? extends CachingMapper> cachingMapperClass;
234
235    @XNode("cachingMapper@enabled")
236    private Boolean cachingMapperEnabled;
237
238    public boolean getCachingMapperEnabled() {
239        return defaultTrue(cachingMapperEnabled);
240    }
241
242    @XNodeMap(value = "cachingMapper/property", key = "@name", type = HashMap.class, componentType = String.class)
243    public Map<String, String> cachingMapperProperties = new HashMap<>();
244
245    @XNode("noDDL")
246    private Boolean noDDL;
247
248    public boolean getNoDDL() {
249        return defaultFalse(noDDL);
250    }
251
252    @XNodeList(value = "sqlInitFile", type = ArrayList.class, componentType = String.class)
253    public List<String> sqlInitFiles = new ArrayList<>(0);
254
255    @XNode("softDelete@enabled")
256    private Boolean softDeleteEnabled;
257
258    public boolean getSoftDeleteEnabled() {
259        return defaultFalse(softDeleteEnabled);
260    }
261
262    protected void setSoftDeleteEnabled(boolean enabled) {
263        softDeleteEnabled = Boolean.valueOf(enabled);
264    }
265
266    @XNode("proxies@enabled")
267    private Boolean proxiesEnabled;
268
269    public boolean getProxiesEnabled() {
270        return defaultTrue(proxiesEnabled);
271    }
272
273    protected void setProxiesEnabled(boolean enabled) {
274        proxiesEnabled = Boolean.valueOf(enabled);
275    }
276
277    @XNode("idType")
278    public String idType; // "varchar", "uuid", "sequence"
279
280    @XNode("clustering@id")
281    private String clusterNodeId;
282
283    public String getClusterNodeId() {
284        return clusterNodeId;
285    }
286
287    @XNode("clustering@enabled")
288    private Boolean clusteringEnabled;
289
290    public boolean getClusteringEnabled() {
291        return defaultFalse(clusteringEnabled);
292    }
293
294    protected void setClusteringEnabled(boolean enabled) {
295        clusteringEnabled = Boolean.valueOf(enabled);
296    }
297
298    @XNode("clustering@delay")
299    private Long clusteringDelay;
300
301    public long getClusteringDelay() {
302        return clusteringDelay == null ? 0 : clusteringDelay.longValue();
303    }
304
305    protected void setClusteringDelay(long delay) {
306        clusteringDelay = Long.valueOf(delay);
307    }
308
309    @XNodeList(value = "schema/field", type = ArrayList.class, componentType = FieldDescriptor.class)
310    public List<FieldDescriptor> schemaFields = new ArrayList<>(0);
311
312    @XNode("schema/arrayColumns")
313    private Boolean arrayColumns;
314
315    public boolean getArrayColumns() {
316        return defaultFalse(arrayColumns);
317    }
318
319    public void setArrayColumns(boolean enabled) {
320        arrayColumns = Boolean.valueOf(enabled);
321    }
322
323    @XNode("indexing/fulltext@disabled")
324    private Boolean fulltextDisabled;
325
326    public boolean getFulltextDisabled() {
327        return defaultFalse(fulltextDisabled);
328    }
329
330    public void setFulltextDisabled(boolean disabled) {
331        fulltextDisabled = Boolean.valueOf(disabled);
332    }
333
334    @XNode("indexing/fulltext@searchDisabled")
335    private Boolean fulltextSearchDisabled;
336
337    public boolean getFulltextSearchDisabled() {
338        if (getFulltextDisabled()) {
339            return true;
340        }
341        return defaultFalse(fulltextSearchDisabled);
342    }
343
344    public void setFulltextSearchDisabled(boolean disabled) {
345        fulltextSearchDisabled = Boolean.valueOf(disabled);
346    }
347
348    @XNode("indexing/fulltext@analyzer")
349    public String fulltextAnalyzer;
350
351    @XNode("indexing/fulltext@parser")
352    public String fulltextParser;
353
354    @XNode("indexing/fulltext@catalog")
355    public String fulltextCatalog;
356
357    @XNode("indexing/queryMaker@class")
358    public void setQueryMakerDeprecated(String klass) {
359        log.warn("Setting queryMaker from repository configuration is now deprecated");
360    }
361
362    @XNodeList(value = "indexing/fulltext/index", type = ArrayList.class, componentType = FulltextIndexDescriptor.class)
363    public List<FulltextIndexDescriptor> fulltextIndexes = new ArrayList<>(0);
364
365    @XNodeList(value = "indexing/excludedTypes/type", type = HashSet.class, componentType = String.class)
366    public Set<String> fulltextExcludedTypes = new HashSet<>(0);
367
368    @XNodeList(value = "indexing/includedTypes/type", type = HashSet.class, componentType = String.class)
369    public Set<String> fulltextIncludedTypes = new HashSet<>(0);
370
371    // compat
372    @XNodeList(value = "indexing/neverPerDocumentFacets/facet", type = HashSet.class, componentType = String.class)
373    public Set<String> neverPerInstanceMixins = new HashSet<>(0);
374
375    @XNode("pathOptimizations@enabled")
376    private Boolean pathOptimizationsEnabled;
377
378    public boolean getPathOptimizationsEnabled() {
379        return defaultTrue(pathOptimizationsEnabled);
380    }
381
382    protected void setPathOptimizationsEnabled(boolean enabled) {
383        pathOptimizationsEnabled = Boolean.valueOf(enabled);
384    }
385
386    /* @since 5.7 */
387    @XNode("pathOptimizations@version")
388    private Integer pathOptimizationsVersion;
389
390    public int getPathOptimizationsVersion() {
391        return pathOptimizationsVersion == null ? DEFAULT_PATH_OPTIM_VERSION : pathOptimizationsVersion.intValue();
392    }
393
394    @XNode("aclOptimizations@enabled")
395    private Boolean aclOptimizationsEnabled;
396
397    public boolean getAclOptimizationsEnabled() {
398        return defaultTrue(aclOptimizationsEnabled);
399    }
400
401    protected void setAclOptimizationsEnabled(boolean enabled) {
402        aclOptimizationsEnabled = Boolean.valueOf(enabled);
403    }
404
405    /* @since 5.4.2 */
406    @XNode("aclOptimizations@readAclMaxSize")
407    private Integer readAclMaxSize;
408
409    public int getReadAclMaxSize() {
410        return readAclMaxSize == null ? DEFAULT_READ_ACL_MAX_SIZE : readAclMaxSize.intValue();
411    }
412
413    @XNode("usersSeparator@key")
414    public String usersSeparatorKey;
415
416    @XNode("xa-datasource")
417    public String xaDataSourceName;
418
419    @XNodeMap(value = "property", key = "@name", type = HashMap.class, componentType = String.class)
420    public Map<String, String> properties = new HashMap<>();
421
422    public RepositoryDescriptor() {
423    }
424
425    /** Copy constructor. */
426    public RepositoryDescriptor(RepositoryDescriptor other) {
427        name = other.name;
428        label = other.label;
429        isDefault = other.isDefault;
430        repositoryFactoryClass = other.repositoryFactoryClass;
431        pool = other.pool == null ? null : new NuxeoConnectionManagerConfiguration(other.pool);
432        backendClass = other.backendClass;
433        clusterInvalidatorClass = other.clusterInvalidatorClass;
434        cachingMapperClass = other.cachingMapperClass;
435        cachingMapperEnabled = other.cachingMapperEnabled;
436        cachingMapperProperties = new HashMap<>(other.cachingMapperProperties);
437        noDDL = other.noDDL;
438        sqlInitFiles = new ArrayList<>(other.sqlInitFiles);
439        softDeleteEnabled = other.softDeleteEnabled;
440        proxiesEnabled = other.proxiesEnabled;
441        schemaFields = FieldDescriptor.copyList(other.schemaFields);
442        arrayColumns = other.arrayColumns;
443        idType = other.idType;
444        clusterNodeId = other.clusterNodeId;
445        clusteringEnabled = other.clusteringEnabled;
446        clusteringDelay = other.clusteringDelay;
447        fulltextDisabled = other.fulltextDisabled;
448        fulltextSearchDisabled = other.fulltextSearchDisabled;
449        fulltextAnalyzer = other.fulltextAnalyzer;
450        fulltextParser = other.fulltextParser;
451        fulltextCatalog = other.fulltextCatalog;
452        fulltextIndexes = FulltextIndexDescriptor.copyList(other.fulltextIndexes);
453        fulltextExcludedTypes = new HashSet<>(other.fulltextExcludedTypes);
454        fulltextIncludedTypes = new HashSet<>(other.fulltextIncludedTypes);
455        neverPerInstanceMixins = other.neverPerInstanceMixins;
456        pathOptimizationsEnabled = other.pathOptimizationsEnabled;
457        pathOptimizationsVersion = other.pathOptimizationsVersion;
458        aclOptimizationsEnabled = other.aclOptimizationsEnabled;
459        readAclMaxSize = other.readAclMaxSize;
460        usersSeparatorKey = other.usersSeparatorKey;
461        xaDataSourceName = other.xaDataSourceName;
462        properties = new HashMap<>(other.properties);
463    }
464
465    public void merge(RepositoryDescriptor other) {
466        if (other.name != null) {
467            name = other.name;
468        }
469        if (other.label != null) {
470            label = other.label;
471        }
472        if (other.isDefault != null) {
473            isDefault = other.isDefault;
474        }
475        if (other.repositoryFactoryClass != null) {
476            repositoryFactoryClass = other.repositoryFactoryClass;
477        }
478        if (other.pool != null) {
479            pool = new NuxeoConnectionManagerConfiguration(other.pool);
480        }
481        if (other.backendClass != null) {
482            backendClass = other.backendClass;
483        }
484        if (other.clusterInvalidatorClass != null) {
485            clusterInvalidatorClass = other.clusterInvalidatorClass;
486        }
487        if (other.cachingMapperClass != null) {
488            cachingMapperClass = other.cachingMapperClass;
489        }
490        if (other.cachingMapperEnabled != null) {
491            cachingMapperEnabled = other.cachingMapperEnabled;
492        }
493        cachingMapperProperties.putAll(other.cachingMapperProperties);
494        if (other.noDDL != null) {
495            noDDL = other.noDDL;
496        }
497        sqlInitFiles.addAll(other.sqlInitFiles);
498        if (other.softDeleteEnabled != null) {
499            softDeleteEnabled = other.softDeleteEnabled;
500        }
501        if (other.proxiesEnabled != null) {
502            proxiesEnabled = other.proxiesEnabled;
503        }
504        if (other.idType != null) {
505            idType = other.idType;
506        }
507        if (other.clusterNodeId != null) {
508            clusterNodeId = other.clusterNodeId;
509        }
510        if (other.clusteringEnabled != null) {
511            clusteringEnabled = other.clusteringEnabled;
512        }
513        if (other.clusteringDelay != null) {
514            clusteringDelay = other.clusteringDelay;
515        }
516        for (FieldDescriptor of : other.schemaFields) {
517            boolean append = true;
518            for (FieldDescriptor f : schemaFields) {
519                if (f.field.equals(of.field)) {
520                    f.merge(of);
521                    append = false;
522                    break;
523                }
524            }
525            if (append) {
526                schemaFields.add(of);
527            }
528        }
529        if (other.arrayColumns != null) {
530            arrayColumns = other.arrayColumns;
531        }
532        if (other.fulltextDisabled != null) {
533            fulltextDisabled = other.fulltextDisabled;
534        }
535        if (other.fulltextSearchDisabled != null) {
536            fulltextSearchDisabled = other.fulltextSearchDisabled;
537        }
538        if (other.fulltextAnalyzer != null) {
539            fulltextAnalyzer = other.fulltextAnalyzer;
540        }
541        if (other.fulltextParser != null) {
542            fulltextParser = other.fulltextParser;
543        }
544        if (other.fulltextCatalog != null) {
545            fulltextCatalog = other.fulltextCatalog;
546        }
547        for (FulltextIndexDescriptor oi : other.fulltextIndexes) {
548            boolean append = true;
549            for (FulltextIndexDescriptor i : fulltextIndexes) {
550                if (ObjectUtils.equals(i.name, oi.name)) {
551                    i.merge(oi);
552                    append = false;
553                    break;
554                }
555            }
556            if (append) {
557                fulltextIndexes.add(oi);
558            }
559        }
560        fulltextExcludedTypes.addAll(other.fulltextExcludedTypes);
561        fulltextIncludedTypes.addAll(other.fulltextIncludedTypes);
562        neverPerInstanceMixins.addAll(other.neverPerInstanceMixins);
563        if (other.pathOptimizationsEnabled != null) {
564            pathOptimizationsEnabled = other.pathOptimizationsEnabled;
565        }
566        if (other.pathOptimizationsVersion != null) {
567            pathOptimizationsVersion = other.pathOptimizationsVersion;
568        }
569        if (other.aclOptimizationsEnabled != null) {
570            aclOptimizationsEnabled = other.aclOptimizationsEnabled;
571        }
572        if (other.readAclMaxSize != null) {
573            readAclMaxSize = other.readAclMaxSize;
574        }
575        if (other.usersSeparatorKey != null) {
576            usersSeparatorKey = other.usersSeparatorKey;
577        }
578        if (other.xaDataSourceName != null) {
579            xaDataSourceName = other.xaDataSourceName;
580        }
581        properties.putAll(other.properties);
582    }
583
584}