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