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