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