001/* 002 * (C) Copyright 2017-2019 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 * Funsho David 018 * 019 */ 020 021package org.nuxeo.directory.mongodb; 022 023import static javax.servlet.http.HttpServletResponse.SC_CONFLICT; 024import static org.apache.commons.lang3.StringUtils.isBlank; 025import static org.nuxeo.directory.mongodb.MongoDBSerializationHelper.MONGODB_ID; 026import static org.nuxeo.directory.mongodb.MongoDBSerializationHelper.MONGODB_SEQ; 027 028import java.io.Serializable; 029import java.util.ArrayList; 030import java.util.Calendar; 031import java.util.Collections; 032import java.util.HashMap; 033import java.util.LinkedList; 034import java.util.List; 035import java.util.Map; 036import java.util.Map.Entry; 037import java.util.Set; 038import java.util.regex.Pattern; 039import java.util.stream.Collectors; 040 041import org.apache.commons.lang3.StringUtils; 042import org.bson.Document; 043import org.bson.conversions.Bson; 044import org.nuxeo.ecm.core.api.DocumentModel; 045import org.nuxeo.ecm.core.api.DocumentModelList; 046import org.nuxeo.ecm.core.api.PropertyException; 047import org.nuxeo.ecm.core.api.impl.DocumentModelListImpl; 048import org.nuxeo.ecm.core.api.model.Property; 049import org.nuxeo.ecm.core.api.security.SecurityConstants; 050import org.nuxeo.ecm.core.query.QueryParseException; 051import org.nuxeo.ecm.core.query.sql.model.Expression; 052import org.nuxeo.ecm.core.query.sql.model.OrderByExpr; 053import org.nuxeo.ecm.core.query.sql.model.OrderByList; 054import org.nuxeo.ecm.core.query.sql.model.QueryBuilder; 055import org.nuxeo.ecm.core.schema.types.Field; 056import org.nuxeo.ecm.core.schema.types.Type; 057import org.nuxeo.ecm.core.schema.types.primitives.IntegerType; 058import org.nuxeo.ecm.core.schema.types.primitives.LongType; 059import org.nuxeo.ecm.core.schema.types.primitives.StringType; 060import org.nuxeo.ecm.core.storage.State; 061import org.nuxeo.ecm.core.storage.mongodb.MongoDBAbstractQueryBuilder; 062import org.nuxeo.ecm.core.storage.mongodb.MongoDBConverter; 063import org.nuxeo.ecm.directory.BaseSession; 064import org.nuxeo.ecm.directory.DirectoryException; 065import org.nuxeo.ecm.directory.OperationNotAllowedException; 066import org.nuxeo.ecm.directory.PasswordHelper; 067import org.nuxeo.ecm.directory.Reference; 068import org.nuxeo.ecm.directory.Session; 069 070import com.mongodb.MongoWriteException; 071import com.mongodb.client.FindIterable; 072import com.mongodb.client.MongoCollection; 073import com.mongodb.client.MongoCursor; 074import com.mongodb.client.model.FindOneAndUpdateOptions; 075import com.mongodb.client.model.ReturnDocument; 076import com.mongodb.client.model.Updates; 077import com.mongodb.client.result.DeleteResult; 078import com.mongodb.client.result.UpdateResult; 079 080/** 081 * MongoDB implementation of a {@link Session} 082 * 083 * @since 9.1 084 */ 085public class MongoDBSession extends BaseSession { 086 087 public MongoDBSession(MongoDBDirectory directory) { 088 super(directory, MongoDBReference.class); 089 } 090 091 @Override 092 public MongoDBDirectory getDirectory() { 093 return (MongoDBDirectory) directory; 094 } 095 096 @Override 097 public DocumentModel getEntryFromSource(String id, boolean fetchReferences) { 098 String idFieldName = getPrefixedIdField(); 099 DocumentModelList result = doQuery(Collections.singletonMap(idFieldName, id), Collections.emptySet(), 100 Collections.emptyMap(), fetchReferences, 1, 0, false); 101 102 if (result.isEmpty()) { 103 return null; 104 } 105 106 DocumentModel docModel = result.get(0); 107 108 if (isMultiTenant()) { 109 // check that the entry is from the current tenant, or no tenant 110 // at all 111 if (!checkEntryTenantId((String) docModel.getProperty(schemaName, TENANT_ID_FIELD))) { 112 return null; 113 } 114 } 115 return docModel; 116 } 117 118 @Override 119 protected DocumentModel createEntryWithoutReferences(Map<String, Object> fieldMap) { 120 // Make a copy of fieldMap to avoid modifying it 121 fieldMap = new HashMap<>(fieldMap); 122 123 // Filter out reference fields for creation as we keep it in a different collection 124 Map<String, Object> newDocMap = fieldMap.entrySet() 125 .stream() 126 .filter(entry -> getDirectory().getReferences(entry.getKey()) == null) 127 .collect(HashMap::new, (m, v) -> m.put(v.getKey(), v.getValue()), 128 HashMap::putAll); 129 Map<String, Field> schemaFieldMap = directory.getSchemaFieldMap(); 130 String idFieldName = getPrefixedIdField(); 131 String id = String.valueOf(fieldMap.get(idFieldName)); 132 if (autoincrementId) { 133 Document filter = MongoDBSerializationHelper.fieldMapToBson(MONGODB_ID, directoryName); 134 Bson update = Updates.inc(MONGODB_SEQ, 1L); 135 FindOneAndUpdateOptions options = new FindOneAndUpdateOptions().returnDocument(ReturnDocument.AFTER); 136 Long longId = getCountersCollection().findOneAndUpdate(filter, update, options).getLong(MONGODB_SEQ); 137 fieldMap.put(idFieldName, longId); 138 newDocMap.put(idFieldName, longId); 139 id = String.valueOf(longId); 140 } 141 142 if (isMultiTenant()) { 143 String tenantId = getCurrentTenantId(); 144 if (StringUtils.isNotBlank(tenantId)) { 145 fieldMap.put(TENANT_ID_FIELD, tenantId); 146 newDocMap.put(TENANT_ID_FIELD, tenantId); 147 if (computeMultiTenantId) { 148 id = computeMultiTenantDirectoryId(tenantId, id); 149 fieldMap.put(idFieldName, id); 150 newDocMap.put(idFieldName, id); 151 } 152 } 153 } 154 155 // Check if the entry already exists 156 if (hasEntry0(id)) { 157 throw new DirectoryException( 158 String.format("Entry with id %s already exists in directory %s", id, directory.getName()), 159 SC_CONFLICT); 160 } 161 162 try { 163 164 for (Map.Entry<String, Field> entry : schemaFieldMap.entrySet()) { 165 Field field = entry.getValue(); 166 if (field != null) { 167 String fieldName = field.getName().getPrefixedName(); 168 Type type = field.getType(); 169 newDocMap.computeIfPresent(fieldName, (k, v) -> convertToType(v, type)); 170 // Load default values if defined and not present in the map 171 Object defaultValue = field.getDefaultValue(); 172 if (defaultValue != null) { 173 newDocMap.putIfAbsent(fieldName, defaultValue); 174 } 175 } 176 } 177 Document bson = MongoDBSerializationHelper.fieldMapToBson(newDocMap); 178 String password = (String) newDocMap.get(getPrefixedPasswordField()); 179 if (password != null && !PasswordHelper.isHashed(password)) { 180 password = PasswordHelper.hashPassword(password, passwordHashAlgorithm); 181 bson.append(getPrefixedPasswordField(), password); 182 } 183 getCollection().insertOne(bson); 184 } catch (MongoWriteException e) { 185 throw new DirectoryException(e); 186 } 187 return createEntryModel(null, schemaName, String.valueOf(fieldMap.get(idFieldName)), fieldMap, isReadOnly()); 188 } 189 190 protected Object convertToType(Object value, Type type) { 191 Object result = value; 192 if (value instanceof String) { 193 if (type instanceof IntegerType) { 194 result = Integer.valueOf((String) value); 195 } else if (type instanceof LongType) { 196 result = Long.valueOf((String) value); 197 } 198 } else if (value instanceof Number) { 199 if (type instanceof LongType && value instanceof Integer) { 200 result = Long.valueOf((Integer) value); 201 } else if (type instanceof StringType) { 202 result = value.toString(); 203 } 204 } 205 return result; 206 } 207 208 @Override 209 protected List<String> updateEntryWithoutReferences(DocumentModel docModel) { 210 Map<String, Object> fieldMap = new HashMap<>(); 211 List<String> referenceFieldList = new LinkedList<>(); 212 213 if (isMultiTenant()) { 214 // can only update entry from the current tenant 215 String tenantId = getCurrentTenantId(); 216 if (StringUtils.isNotBlank(tenantId)) { 217 String entryTenantId = (String) docModel.getProperty(schemaName, TENANT_ID_FIELD); 218 if (isBlank(entryTenantId) || !entryTenantId.equals(tenantId)) { 219 throw new OperationNotAllowedException("Operation not allowed in the current tenant context", 220 "label.directory.error.multi.tenant.operationNotAllowed", null); 221 } 222 } 223 } 224 225 List<String> fields = directory.getSchemaFieldMap() 226 .values() 227 .stream() 228 .map(field -> field.getName().getPrefixedName()) 229 .collect(Collectors.toList()); 230 String idFieldName = getPrefixedIdField(); 231 String passwordFieldName = getPrefixedPasswordField(); 232 233 for (String fieldName : fields) { 234 if (fieldName.equals(idFieldName)) { 235 continue; 236 } 237 Property prop = docModel.getPropertyObject(schemaName, fieldName); 238 if (prop == null || !prop.isDirty() 239 || (fieldName.equals(passwordFieldName) && StringUtils.isEmpty((String) prop.getValue()))) { 240 continue; 241 } 242 if (getDirectory().isReference(fieldName)) { 243 referenceFieldList.add(fieldName); 244 } else { 245 Serializable value = prop.getValue(); 246 if (fieldName.equals(passwordFieldName)) { 247 value = PasswordHelper.hashPassword((String) value, passwordHashAlgorithm); 248 } 249 if (value instanceof Calendar) { 250 value = ((Calendar) value).getTime(); 251 } 252 fieldMap.put(prop.getName(), value); 253 } 254 } 255 256 String id = docModel.getId(); 257 Object idFieldValue = convertToType(id, getIdFieldType()); 258 Document bson = MongoDBSerializationHelper.fieldMapToBson(idFieldName, idFieldValue); 259 260 List<Bson> updates = fieldMap.entrySet() 261 .stream() 262 .map(e -> Updates.set(e.getKey(), e.getValue())) 263 .collect(Collectors.toList()); 264 265 if (!updates.isEmpty()) { 266 try { 267 UpdateResult result = getCollection().updateOne(bson, Updates.combine(updates)); 268 // Throw an error if no document matched the update 269 if (!result.wasAcknowledged()) { 270 throw new DirectoryException( 271 "Error while updating the entry, the request has not been acknowledged by the server"); 272 } 273 if (result.getMatchedCount() == 0) { 274 throw new DirectoryException( 275 String.format("Error while updating the entry, no document was found with the id %s", id)); 276 } 277 } catch (MongoWriteException e) { 278 throw new DirectoryException(e); 279 } 280 } 281 return referenceFieldList; 282 } 283 284 @Override 285 public void deleteEntryWithoutReferences(String id) { 286 try { 287 String idFieldName = getPrefixedIdField(); 288 Object idFieldValue = convertToType(id, getIdFieldType()); 289 DeleteResult result = getCollection().deleteOne( 290 MongoDBSerializationHelper.fieldMapToBson(idFieldName, idFieldValue)); 291 if (!result.wasAcknowledged()) { 292 throw new DirectoryException( 293 "Error while deleting the entry, the request has not been acknowledged by the server"); 294 } 295 } catch (MongoWriteException e) { 296 throw new DirectoryException(e); 297 } 298 } 299 300 @Override 301 public DocumentModelList query(Map<String, Serializable> filter, Set<String> fulltext, Map<String, String> orderBy, 302 boolean fetchReferences, int limit, int offset) { 303 return doQuery(filter, fulltext, orderBy, fetchReferences, limit, offset, true); 304 } 305 306 protected DocumentModelList doQuery(Map<String, Serializable> filter, Set<String> fulltext, 307 Map<String, String> orderBy, boolean fetchReferences, int limit, int offset, boolean checkTenantId) { 308 309 if (!hasPermission(SecurityConstants.READ)) { 310 return new DocumentModelListImpl(); 311 } 312 313 Map<String, Serializable> filterMap = new HashMap<>(filter); 314 315 if (checkTenantId && isMultiTenant()) { 316 // filter entries on the tenantId field also 317 String tenantId = getCurrentTenantId(); 318 if (StringUtils.isNotBlank(tenantId)) { 319 filterMap.put(TENANT_ID_FIELD, tenantId); 320 } 321 } 322 323 // Remove password as it is not possible to do queries with it 324 String passwordFieldName = getPrefixedPasswordField(); 325 filterMap.remove(passwordFieldName); 326 Document bson = buildQuery(filterMap, fulltext); 327 328 DocumentModelList entries = new DocumentModelListImpl(); 329 330 FindIterable<Document> results = getCollection().find(bson).skip(offset); 331 if (limit > 0) { 332 results.limit(limit); 333 } 334 for (Document resultDoc : results) { 335 336 // Cast object to document model 337 Map<String, Object> fieldMap = MongoDBSerializationHelper.bsonToFieldMap(resultDoc); 338 // Remove password from results 339 if (!readAllColumns) { 340 fieldMap.remove(passwordFieldName); 341 } 342 DocumentModel doc = fieldMapToDocumentModel(fieldMap); 343 344 if (fetchReferences) { 345 Map<String, List<String>> targetIdsMap = new HashMap<>(); 346 for (Reference reference : directory.getReferences()) { 347 List<String> targetIds; 348 if (reference instanceof MongoDBReference) { 349 MongoDBReference mongoReference = (MongoDBReference) reference; 350 targetIds = mongoReference.getTargetIdsForSource(doc.getId(), this); 351 } else { 352 targetIds = reference.getTargetIdsForSource(doc.getId()); 353 } 354 targetIds = new ArrayList<>(targetIds); 355 Collections.sort(targetIds); 356 String fieldName = reference.getFieldName(); 357 targetIdsMap.computeIfAbsent(fieldName, key -> new ArrayList<>()).addAll(targetIds); 358 } 359 for (Map.Entry<String, List<String>> entry : targetIdsMap.entrySet()) { 360 String fieldName = entry.getKey(); 361 List<String> targetIds = entry.getValue(); 362 try { 363 doc.setProperty(schemaName, fieldName, targetIds); 364 } catch (PropertyException e) { 365 throw new DirectoryException(e); 366 } 367 } 368 } 369 entries.add(doc); 370 } 371 372 if (orderBy != null && !orderBy.isEmpty()) { 373 getDirectory().orderEntries(entries, orderBy); 374 } 375 376 return entries; 377 } 378 379 protected Document buildQuery(Map<String, Serializable> fieldMap, Set<String> fulltext) { 380 Map<String, Field> schemaFieldMap = directory.getSchemaFieldMap(); 381 Document bson = new Document(); 382 for (Map.Entry<String, Serializable> entry : fieldMap.entrySet()) { 383 Field field = schemaFieldMap.values() 384 .stream() 385 .filter(v -> v.getName().getPrefixedName().equals(entry.getKey())) 386 .findFirst() 387 .orElse(null); 388 389 Serializable v = entry.getValue(); 390 Object value = (field != null) ? MongoDBSerializationHelper.valueToBson(v, field.getType()) 391 : MongoDBSerializationHelper.valueToBson(v); 392 String key = entry.getKey(); 393 if (fulltext != null && fulltext.contains(key)) { 394 String val = String.valueOf(value); 395 val = val.replaceAll("%+", ".*"); 396 switch (substringMatchType) { 397 case subany: 398 addField(bson, key, Pattern.compile(val, Pattern.CASE_INSENSITIVE)); 399 break; 400 case subinitial: 401 addField(bson, key, Pattern.compile('^' + val, Pattern.CASE_INSENSITIVE)); 402 break; 403 case subfinal: 404 addField(bson, key, Pattern.compile(val + '$', Pattern.CASE_INSENSITIVE)); 405 break; 406 } 407 } else { 408 addField(bson, key, value); 409 } 410 } 411 return bson; 412 } 413 414 protected void addField(Document bson, String key, Object value) { 415 String keyFieldName = key; 416 Field field = directory.getSchemaFieldMap().get(key); 417 if (field != null) { 418 keyFieldName = field.getName().getPrefixedName(); 419 } 420 bson.put(keyFieldName, value); 421 } 422 423 @Override 424 public DocumentModelList query(QueryBuilder queryBuilder, boolean fetchReferences) { 425 if (!hasPermission(SecurityConstants.READ)) { 426 return new DocumentModelListImpl(); 427 } 428 String passwordFieldName = getPrefixedPasswordField(); 429 if (FieldDetector.hasField(queryBuilder.predicate(), getPasswordField()) 430 || FieldDetector.hasField(queryBuilder.predicate(), passwordFieldName)) { 431 throw new DirectoryException("Cannot filter on password"); 432 } 433 queryBuilder = addTenantId(queryBuilder); 434 435 MongoDBConverter converter = new MongoDBConverter(); 436 MongoDBDirectoryQueryBuilder builder = new MongoDBDirectoryQueryBuilder(converter, queryBuilder.predicate()); 437 builder.walk(); 438 Document filter = builder.getQuery(); 439 int limit = Math.max(0, (int) queryBuilder.limit()); 440 int offset = Math.max(0, (int) queryBuilder.offset()); 441 boolean countTotal = queryBuilder.countTotal(); 442 // we should also use getDirectory().getDescriptor().getQuerySizeLimit() like in SQL 443 Document sort = builder.walkOrderBy(queryBuilder.orders()); 444 445 DocumentModelListImpl entries = new DocumentModelListImpl(); 446 447 // use a MongoCursor instead of a simple MongoIterable to avoid fetching everything at once 448 try (MongoCursor<Document> cursor = getCollection().find(filter) 449 .limit(limit) 450 .skip(offset) 451 .sort(sort) 452 .iterator()) { 453 for (Document doc : (Iterable<Document>) () -> cursor) { 454 if (!readAllColumns) { 455 // remove password from results 456 doc.remove(passwordFieldName); 457 } 458 State state = converter.bsonToState(doc); 459 Map<String, Object> fieldMap = new HashMap<>(); 460 for (Entry<String, Serializable> es : state.entrySet()) { 461 fieldMap.put(es.getKey(), es.getValue()); 462 } 463 DocumentModel docModel = fieldMapToDocumentModel(fieldMap); 464 465 if (fetchReferences) { 466 Map<String, List<String>> targetIdsMap = new HashMap<>(); 467 for (Reference reference : directory.getReferences()) { 468 List<String> targetIds; 469 if (reference instanceof MongoDBReference) { 470 MongoDBReference mongoReference = (MongoDBReference) reference; 471 targetIds = mongoReference.getTargetIdsForSource(docModel.getId(), this); 472 } else { 473 targetIds = reference.getTargetIdsForSource(docModel.getId()); 474 } 475 targetIds = new ArrayList<>(targetIds); 476 Collections.sort(targetIds); 477 String fieldName = reference.getFieldName(); 478 targetIdsMap.computeIfAbsent(fieldName, key -> new ArrayList<>()).addAll(targetIds); 479 } 480 for (Entry<String, List<String>> entry : targetIdsMap.entrySet()) { 481 String fieldName = entry.getKey(); 482 List<String> targetIds = entry.getValue(); 483 docModel.setProperty(schemaName, fieldName, targetIds); 484 } 485 } 486 entries.add(docModel); 487 } 488 } 489 if (limit != 0 || offset != 0) { 490 long count; 491 if (countTotal) { 492 // we have to do an additional query to count the total number of results 493 count = getCollection().countDocuments(filter); 494 } else { 495 count = -2; // unknown 496 } 497 entries.setTotalSize(count); 498 } 499 return entries; 500 } 501 502 @Override 503 public List<String> queryIds(QueryBuilder queryBuilder) { 504 if (!hasPermission(SecurityConstants.READ)) { 505 return Collections.emptyList(); 506 } 507 if (FieldDetector.hasField(queryBuilder.predicate(), getPasswordField()) 508 || FieldDetector.hasField(queryBuilder.predicate(), getPrefixedPasswordField())) { 509 throw new DirectoryException("Cannot filter on password"); 510 } 511 queryBuilder = addTenantId(queryBuilder); 512 513 MongoDBConverter converter = new MongoDBConverter(); 514 MongoDBDirectoryQueryBuilder builder = new MongoDBDirectoryQueryBuilder(converter, queryBuilder.predicate()); 515 builder.walk(); 516 Document filter = builder.getQuery(); 517 String idFieldName = getPrefixedIdField(); 518 Document projection = new Document(idFieldName, 1L); 519 int limit = Math.max(0, (int) queryBuilder.limit()); 520 int offset = Math.max(0, (int) queryBuilder.offset()); 521 // we should also use getDirectory().getDescriptor().getQuerySizeLimit() like in SQL 522 Document sort = builder.walkOrderBy(queryBuilder.orders()); 523 524 List<String> ids = new ArrayList<>(); 525 526 // use a MongoCursor instead of a simple MongoIterable to avoid fetching everything at once 527 try (MongoCursor<Document> cursor = getCollection().find(filter) 528 .projection(projection) 529 .limit(limit) 530 .skip(offset) 531 .sort(sort) 532 .iterator()) { 533 for (Document doc : (Iterable<Document>) () -> cursor) { 534 State state = converter.bsonToState(doc); 535 String id = getIdFromState(state); 536 ids.add(id); 537 } 538 } 539 return ids; 540 } 541 542 /** 543 * MongoDB Query Builder that knows how to resolved directory properties. 544 * 545 * @since 10.3 546 */ 547 public class MongoDBDirectoryQueryBuilder extends MongoDBAbstractQueryBuilder { 548 549 public MongoDBDirectoryQueryBuilder(MongoDBConverter converter, Expression expression) { 550 super(converter, expression); 551 } 552 553 @Override 554 protected Document newDocumentWithField(FieldInfo fieldInfo, Object value) { 555 return new Document(fieldInfo.queryField, convertToType(value, fieldInfo.type)); 556 } 557 558 @Override 559 protected FieldInfo walkReference(String name) { 560 Field field = directory.getSchemaFieldMap().get(name); 561 if (field == null) { 562 throw new QueryParseException("No column: " + name + " for directory: " + getDirectory().getName()); 563 } 564 String key = field.getName().getPrefixedName(); 565 String queryField = stripElemMatchPrefix(key); 566 return new FieldInfo(name, key, queryField, queryField, field.getType()); 567 } 568 569 protected Document walkOrderBy(OrderByList orderByList) { 570 if (orderByList.isEmpty()) { 571 return null; 572 } 573 Document orderBy = new Document(); 574 for (OrderByExpr ob : orderByList) { 575 String field = walkReference(ob.reference).queryField; 576 if (!orderBy.containsKey(field)) { 577 orderBy.put(field, ob.isDescending ? MINUS_ONE : ONE); 578 } 579 } 580 return orderBy; 581 } 582 } 583 584 @Override 585 public void close() { 586 getDirectory().removeSession(this); 587 } 588 589 @Override 590 public boolean authenticate(String username, String password) { 591 Document user = getCollection().find(MongoDBSerializationHelper.fieldMapToBson(getPrefixedIdField(), username)) 592 .first(); 593 if (user == null) { 594 return false; 595 } 596 597 String storedPassword = user.getString(getPrefixedPasswordField()); 598 if (isMultiTenant()) { 599 // check that the entry is from the current tenant, or no tenant at all 600 if (!checkEntryTenantId(user.getString(TENANT_ID_FIELD))) { 601 storedPassword = null; 602 } 603 } 604 605 return PasswordHelper.verifyPassword(password, storedPassword); 606 } 607 608 @Override 609 public boolean isAuthenticating() { 610 return directory.getSchemaFieldMap().containsKey(getPasswordField()); 611 } 612 613 @Override 614 public boolean hasEntry(String id) { 615 return hasEntry0(id); 616 } 617 618 protected boolean hasEntry0(Object id) { 619 String idFieldName = getPrefixedIdField(); 620 Type idFieldType = getIdFieldType(); 621 Object idFieldValue = convertToType(id, idFieldType); 622 return getCollection().countDocuments(MongoDBSerializationHelper.fieldMapToBson(idFieldName, idFieldValue)) > 0; 623 } 624 625 /** 626 * Retrieve the collection associated to this directory 627 * 628 * @return the MongoDB collection 629 */ 630 protected MongoCollection<Document> getCollection() { 631 return getDirectory().getCollection(); 632 } 633 634 /** 635 * Retrieve the counters collection associated to this directory 636 * 637 * @return the MongoDB counters collection 638 */ 639 protected MongoCollection<Document> getCountersCollection() { 640 return getDirectory().getCountersCollection(); 641 } 642 643 protected DocumentModel fieldMapToDocumentModel(Map<String, Object> fieldMap) { 644 String idFieldName = getPrefixedIdField(); 645 if (!fieldMap.containsKey(idFieldName)) { 646 idFieldName = getIdField(); 647 } 648 String id = String.valueOf(fieldMap.get(idFieldName)); 649 return createEntryModel(null, schemaName, id, fieldMap, isReadOnly()); 650 } 651 652 protected String getIdFromState(State state) { 653 String idFieldName = getPrefixedIdField(); 654 if (!state.containsKey(idFieldName)) { 655 idFieldName = getIdField(); 656 } 657 return String.valueOf(state.get(idFieldName)); 658 } 659 660 protected boolean checkEntryTenantId(String entryTenantId) { 661 // check that the entry is from the current tenant, or no tenant at all 662 String tenantId = getCurrentTenantId(); 663 return isBlank(tenantId) || isBlank(entryTenantId) || tenantId.equals(entryTenantId); 664 } 665 666 protected String getPrefixedIdField() { 667 Field idField = directory.getSchemaFieldMap().get(getIdField()); 668 if (idField == null) { 669 return null; 670 } 671 return idField.getName().getPrefixedName(); 672 } 673 674 protected String getPrefixedPasswordField() { 675 Field passwordField = directory.getSchemaFieldMap().get(getPasswordField()); 676 if (passwordField == null) { 677 return null; 678 } 679 return passwordField.getName().getPrefixedName(); 680 } 681 682 protected Type getIdFieldType() { 683 Field idField = directory.getSchemaFieldMap().get(getIdField()); 684 if (idField == null) { 685 return null; 686 } 687 return idField.getType(); 688 } 689 690}