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