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}