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 *     Nuxeo - initial API and implementation
018 *
019 */
020
021package org.nuxeo.ecm.directory;
022
023import java.util.ArrayList;
024import java.util.Arrays;
025import java.util.Collection;
026import java.util.Collections;
027import java.util.HashMap;
028import java.util.LinkedHashMap;
029import java.util.List;
030import java.util.Map;
031
032import org.apache.commons.lang.StringUtils;
033import org.nuxeo.ecm.core.api.DocumentModel;
034import org.nuxeo.ecm.core.api.DocumentModelComparator;
035import org.nuxeo.ecm.core.cache.CacheService;
036import org.nuxeo.ecm.core.schema.SchemaManager;
037import org.nuxeo.ecm.core.schema.types.Field;
038import org.nuxeo.ecm.core.schema.types.Schema;
039import org.nuxeo.ecm.directory.api.DirectoryDeleteConstraint;
040import org.nuxeo.runtime.api.Framework;
041import org.nuxeo.runtime.metrics.MetricsService;
042
043import com.codahale.metrics.Counter;
044import com.codahale.metrics.MetricRegistry;
045import com.codahale.metrics.SharedMetricRegistries;
046
047public abstract class AbstractDirectory implements Directory {
048
049    public final BaseDirectoryDescriptor descriptor;
050
051    protected DirectoryFieldMapper fieldMapper;
052
053    protected final Map<String, List<Reference>> references = new HashMap<>();
054
055    // simple cache system for entry lookups, disabled by default
056    protected final DirectoryCache cache;
057
058    // @since 5.7
059    protected final MetricRegistry registry = SharedMetricRegistries.getOrCreate(MetricsService.class.getName());
060
061    protected final Counter sessionCount;
062
063    protected final Counter sessionMaxCount;
064
065    protected Map<String, Field> schemaFieldMap;
066
067    protected List<String> types = new ArrayList<>();
068
069    protected Class<? extends Reference> referenceClass;
070
071    protected AbstractDirectory(BaseDirectoryDescriptor descriptor, Class<? extends Reference> referenceClass) {
072        this.referenceClass = referenceClass;
073        this.descriptor = descriptor;
074        // is the directory visible in the ui
075        if (descriptor.types != null) {
076            this.types = Arrays.asList(descriptor.types);
077        }
078        if (!descriptor.template && doSanityChecks()) {
079            if (StringUtils.isEmpty(descriptor.idField)) {
080                throw new DirectoryException("idField configuration is missing for directory: " + getName());
081            }
082            if (StringUtils.isEmpty(descriptor.schemaName)) {
083                throw new DirectoryException("schema configuration is missing for directory " + getName());
084            }
085        }
086
087        sessionCount = registry.counter(MetricRegistry.name("nuxeo", "directories", getName(), "sessions", "active"));
088        sessionMaxCount = registry.counter(MetricRegistry.name("nuxeo", "directories", getName(), "sessions", "max"));
089
090        // add references
091        addInverseReferences(descriptor.getInverseReferences());
092        addReferences(descriptor.getReferences());
093
094        // cache parameterization
095        cache = new DirectoryCache(getName());
096        cache.setEntryCacheName(descriptor.cacheEntryName);
097        cache.setEntryCacheWithoutReferencesName(descriptor.cacheEntryWithoutReferencesName);
098        cache.setNegativeCaching(descriptor.negativeCaching);
099
100    }
101
102    protected boolean doSanityChecks() {
103        return true;
104    }
105
106    @Override
107    public String getName() {
108        return descriptor.name;
109    }
110
111    @Override
112    public String getSchema() {
113        return descriptor.schemaName;
114    }
115
116    @Override
117    public String getParentDirectory() {
118        return descriptor.parentDirectory;
119    }
120
121    @Override
122    public String getIdField() {
123        return descriptor.idField;
124    }
125
126    @Override
127    public String getPasswordField() {
128        return descriptor.passwordField;
129    }
130
131    @Override
132    public boolean isReadOnly() {
133        return descriptor.isReadOnly();
134    }
135
136    public void setReadOnly(boolean readOnly) {
137        descriptor.setReadOnly(readOnly);
138    }
139
140    @Override
141    public void invalidateCaches() throws DirectoryException {
142        cache.invalidateAll();
143        for (Reference ref : getReferences()) {
144            Directory targetDir = ref.getTargetDirectory();
145            if (targetDir != null) {
146                targetDir.invalidateDirectoryCache();
147            }
148        }
149    }
150
151    public DirectoryFieldMapper getFieldMapper() {
152        if (fieldMapper == null) {
153            fieldMapper = new DirectoryFieldMapper();
154        }
155        return fieldMapper;
156    }
157
158    @Deprecated
159    @Override
160    public Reference getReference(String referenceFieldName) {
161        List<Reference> refs = getReferences(referenceFieldName);
162        if (refs == null || refs.isEmpty()) {
163            return null;
164        } else if (refs.size() == 1) {
165            return refs.get(0);
166        } else {
167            throw new DirectoryException("Unexpected multiple references for " + referenceFieldName + " in directory "
168                    + getName());
169        }
170    }
171
172    @Override
173    public List<Reference> getReferences(String referenceFieldName) {
174        return references.get(referenceFieldName);
175    }
176
177    public boolean isReference(String referenceFieldName) {
178        return references.containsKey(referenceFieldName);
179    }
180
181    public void addReference(Reference reference) {
182        reference.setSourceDirectoryName(getName());
183        String fieldName = reference.getFieldName();
184        List<Reference> fieldRefs;
185        if (references.containsKey(fieldName)) {
186            fieldRefs = references.get(fieldName);
187        } else {
188            references.put(fieldName, fieldRefs = new ArrayList<>(1));
189        }
190        fieldRefs.add(reference);
191    }
192
193    public void addReferences(Reference[] refs) {
194        for (Reference reference : refs) {
195            addReference(reference);
196        }
197    }
198
199    protected void addInverseReferences(InverseReferenceDescriptor[] references) {
200        if (references != null) {
201            Arrays.stream(references).map(InverseReference::new).forEach(this::addReference);
202        }
203    }
204
205    protected void addReferences(ReferenceDescriptor[] references) {
206        if (references != null) {
207            for (ReferenceDescriptor desc : references) {
208                try {
209                    addReference(referenceClass.getDeclaredConstructor(ReferenceDescriptor.class).newInstance(desc));
210                } catch (ReflectiveOperationException e) {
211                    throw new DirectoryException(
212                            "An error occurred while instantiating reference class " + referenceClass.getName(), e);
213                }
214            }
215        }
216    }
217
218    @Override
219    public Collection<Reference> getReferences() {
220        List<Reference> allRefs = new ArrayList<>(2);
221        for (List<Reference> refs : references.values()) {
222            allRefs.addAll(refs);
223        }
224        return allRefs;
225    }
226
227    /**
228     * Helper method to order entries.
229     *
230     * @param entries the list of entries.
231     * @param orderBy an ordered map of field name -> "asc" or "desc".
232     */
233    public void orderEntries(List<DocumentModel> entries, Map<String, String> orderBy) throws DirectoryException {
234        Collections.sort(entries, new DocumentModelComparator(getSchema(), orderBy));
235    }
236
237    @Override
238    public DirectoryCache getCache() {
239        return cache;
240    }
241
242    public void removeSession(Session session) {
243        sessionCount.dec();
244    }
245
246    public void addSession(Session session) {
247        sessionCount.inc();
248        if (sessionCount.getCount() > sessionMaxCount.getCount()) {
249            sessionMaxCount.inc();
250        }
251    }
252
253    @Override
254    public void invalidateDirectoryCache() throws DirectoryException {
255        getCache().invalidateAll();
256    }
257
258    @Override
259    public boolean isMultiTenant() {
260        return false;
261    }
262
263    @Override
264    public void shutdown() {
265        sessionCount.dec(sessionCount.getCount());
266        sessionMaxCount.dec(sessionMaxCount.getCount());
267    }
268
269    /**
270     * since @8.4
271     */
272    @Override
273    public List<String> getTypes() {
274        return types;
275    }
276
277    /**
278     * @since 8.4
279     */
280    @Override
281    public List<DirectoryDeleteConstraint> getDirectoryDeleteConstraints() {
282        return descriptor.getDeleteConstraints();
283    }
284
285    /*
286     * Initializes schemaFieldMap. Note that this cannot be called from the Directory constructor because the
287     * SchemaManager initialization itself requires access to directories (and therefore their construction) for fields
288     * having entry resolvers. So an infinite recursion must be avoided.
289     */
290    protected void initSchemaFieldMap() {
291        SchemaManager schemaManager = Framework.getService(SchemaManager.class);
292        Schema schema = schemaManager.getSchema(getSchema());
293        if (schema == null) {
294            throw new DirectoryException(
295                    "Invalid configuration for directory: " + getName() + ", no such schema: " + getSchema());
296        }
297        schemaFieldMap = new LinkedHashMap<>();
298        schema.getFields().forEach(f -> schemaFieldMap.put(f.getName().getLocalName(), f));
299    }
300
301    @Override
302    public Map<String, Field> getSchemaFieldMap() {
303        return schemaFieldMap;
304    }
305
306    protected void fallbackOnDefaultCache() {
307        CacheService cacheService = Framework.getService(CacheService.class);
308        if (cacheService != null) {
309            if (descriptor.cacheEntryName == null) {
310                String cacheEntryName = "cache-" + getName();
311                cache.setEntryCacheName(cacheEntryName);
312                cacheService.registerCache(cacheEntryName);
313            }
314            if (descriptor.cacheEntryWithoutReferencesName == null) {
315                String cacheEntryWithoutReferencesName = "cacheWithoutReference-" + getName();
316                cache.setEntryCacheWithoutReferencesName(cacheEntryWithoutReferencesName);
317                cacheService.registerCache(cacheEntryWithoutReferencesName);
318            }
319        }
320    }
321}