001/* 002 * (C) Copyright 2006-2017 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 * Bogdan Stefanescu 018 * Florent Guillaume 019 * Benoit Delbosc 020 */ 021package org.nuxeo.ecm.core.api; 022 023import static org.nuxeo.ecm.core.api.event.CoreEventConstants.CHANGED_ACL_NAME; 024import static org.nuxeo.ecm.core.api.event.CoreEventConstants.NEW_ACE; 025import static org.nuxeo.ecm.core.api.event.CoreEventConstants.OLD_ACE; 026import static org.nuxeo.ecm.core.api.security.SecurityConstants.ADD_CHILDREN; 027import static org.nuxeo.ecm.core.api.security.SecurityConstants.BROWSE; 028import static org.nuxeo.ecm.core.api.security.SecurityConstants.READ; 029import static org.nuxeo.ecm.core.api.security.SecurityConstants.READ_CHILDREN; 030import static org.nuxeo.ecm.core.api.security.SecurityConstants.READ_LIFE_CYCLE; 031import static org.nuxeo.ecm.core.api.security.SecurityConstants.READ_PROPERTIES; 032import static org.nuxeo.ecm.core.api.security.SecurityConstants.READ_SECURITY; 033import static org.nuxeo.ecm.core.api.security.SecurityConstants.READ_VERSION; 034import static org.nuxeo.ecm.core.api.security.SecurityConstants.REMOVE; 035import static org.nuxeo.ecm.core.api.security.SecurityConstants.REMOVE_CHILDREN; 036import static org.nuxeo.ecm.core.api.security.SecurityConstants.SYSTEM_USERNAME; 037import static org.nuxeo.ecm.core.api.security.SecurityConstants.UNLOCK; 038import static org.nuxeo.ecm.core.api.security.SecurityConstants.WRITE; 039import static org.nuxeo.ecm.core.api.security.SecurityConstants.WRITE_LIFE_CYCLE; 040import static org.nuxeo.ecm.core.api.security.SecurityConstants.WRITE_PROPERTIES; 041import static org.nuxeo.ecm.core.api.security.SecurityConstants.WRITE_SECURITY; 042import static org.nuxeo.ecm.core.api.security.SecurityConstants.WRITE_VERSION; 043 044import java.io.Serializable; 045import java.security.Principal; 046import java.util.ArrayList; 047import java.util.Arrays; 048import java.util.Collection; 049import java.util.Collections; 050import java.util.Comparator; 051import java.util.GregorianCalendar; 052import java.util.HashMap; 053import java.util.List; 054import java.util.Map; 055import java.util.Map.Entry; 056import java.util.stream.Collectors; 057 058import org.apache.commons.logging.Log; 059import org.apache.commons.logging.LogFactory; 060import org.nuxeo.ecm.core.CoreService; 061import org.nuxeo.ecm.core.NXCore; 062import org.nuxeo.ecm.core.api.DocumentModel.DocumentModelRefresh; 063import org.nuxeo.ecm.core.api.event.CoreEventConstants; 064import org.nuxeo.ecm.core.api.event.DocumentEventCategories; 065import org.nuxeo.ecm.core.api.event.DocumentEventTypes; 066import org.nuxeo.ecm.core.api.facet.VersioningDocument; 067import org.nuxeo.ecm.core.api.impl.DocumentModelChildrenIterator; 068import org.nuxeo.ecm.core.api.impl.DocumentModelListImpl; 069import org.nuxeo.ecm.core.api.impl.FacetFilter; 070import org.nuxeo.ecm.core.api.impl.UserPrincipal; 071import org.nuxeo.ecm.core.api.impl.VersionModelImpl; 072import org.nuxeo.ecm.core.api.security.ACE; 073import org.nuxeo.ecm.core.api.security.ACP; 074import org.nuxeo.ecm.core.api.security.SecurityConstants; 075import org.nuxeo.ecm.core.api.security.UserEntry; 076import org.nuxeo.ecm.core.api.security.impl.ACPImpl; 077import org.nuxeo.ecm.core.api.security.impl.UserEntryImpl; 078import org.nuxeo.ecm.core.api.validation.DocumentValidationException; 079import org.nuxeo.ecm.core.api.validation.DocumentValidationReport; 080import org.nuxeo.ecm.core.api.validation.DocumentValidationService; 081import org.nuxeo.ecm.core.event.Event; 082import org.nuxeo.ecm.core.event.EventService; 083import org.nuxeo.ecm.core.event.impl.DocumentEventContext; 084import org.nuxeo.ecm.core.filter.CharacterFilteringService; 085import org.nuxeo.ecm.core.lifecycle.LifeCycleService; 086import org.nuxeo.ecm.core.model.Document; 087import org.nuxeo.ecm.core.model.PathComparator; 088import org.nuxeo.ecm.core.model.Session; 089import org.nuxeo.ecm.core.query.QueryFilter; 090import org.nuxeo.ecm.core.query.QueryParseException; 091import org.nuxeo.ecm.core.query.sql.NXQL; 092import org.nuxeo.ecm.core.query.sql.model.SQLQuery.Transformer; 093import org.nuxeo.ecm.core.schema.DocumentType; 094import org.nuxeo.ecm.core.schema.FacetNames; 095import org.nuxeo.ecm.core.schema.SchemaManager; 096import org.nuxeo.ecm.core.schema.types.CompositeType; 097import org.nuxeo.ecm.core.schema.types.Schema; 098import org.nuxeo.ecm.core.security.SecurityService; 099import org.nuxeo.ecm.core.versioning.VersioningService; 100import org.nuxeo.runtime.api.Framework; 101import org.nuxeo.runtime.metrics.MetricsService; 102import org.nuxeo.runtime.services.config.ConfigurationService; 103 104import com.codahale.metrics.Counter; 105import com.codahale.metrics.MetricRegistry; 106import com.codahale.metrics.SharedMetricRegistries; 107 108/** 109 * Abstract implementation of the client interface. 110 * <p> 111 * This handles all the aspects that are independent on the final implementation (like running inside a J2EE platform or 112 * not). 113 * <p> 114 * The only aspect not implemented is the session management that should be handled by subclasses. 115 * 116 * @author Bogdan Stefanescu 117 * @author Florent Guillaume 118 */ 119public abstract class AbstractSession implements CoreSession, Serializable { 120 121 public static final NuxeoPrincipal ANONYMOUS = new UserPrincipal("anonymous", new ArrayList<>(), true, false); 122 123 private static final Log log = LogFactory.getLog(CoreSession.class); 124 125 private static final long serialVersionUID = 1L; 126 127 private static final Comparator<? super Document> pathComparator = new PathComparator(); 128 129 public static final String DEFAULT_MAX_RESULTS = "1000"; 130 131 public static final String MAX_RESULTS_PROPERTY = "org.nuxeo.ecm.core.max.results"; 132 133 public static final String LIMIT_RESULTS_PROPERTY = "org.nuxeo.ecm.core.limit.results"; 134 135 public static final String TRASH_KEEP_CHECKED_IN_PROPERTY = "org.nuxeo.trash.keepCheckedIn"; 136 137 // @since 9.1 disable ecm:isLatestVersion and ecm:isLatestMajorVersion updates for performance purpose 138 public static final String DISABLED_ISLATESTVERSION_PROPERTY = "org.nuxeo.core.isLatestVersion.disabled"; 139 140 public static final String BINARY_TEXT_SYS_PROP = "fulltextBinary"; 141 142 private Boolean limitedResults; 143 144 private Long maxResults; 145 146 // @since 5.7.2 147 protected final MetricRegistry registry = SharedMetricRegistries.getOrCreate(MetricsService.class.getName()); 148 149 protected Counter createDocumentCount; 150 151 protected Counter deleteDocumentCount; 152 153 protected Counter updateDocumentCount; 154 155 protected void createMetrics() { 156 createDocumentCount = registry.counter(MetricRegistry.name("nuxeo.repositories", getRepositoryName(), 157 "documents", "create")); 158 deleteDocumentCount = registry.counter(MetricRegistry.name("nuxeo.repositories", getRepositoryName(), 159 "documents", "delete")); 160 updateDocumentCount = registry.counter(MetricRegistry.name("nuxeo.repositories", getRepositoryName(), 161 "documents", "update")); 162 } 163 164 /** 165 * Used to check permissions. 166 */ 167 private transient SecurityService securityService; 168 169 protected SecurityService getSecurityService() { 170 if (securityService == null) { 171 securityService = NXCore.getSecurityService(); 172 } 173 return securityService; 174 } 175 176 private transient VersioningService versioningService; 177 178 protected VersioningService getVersioningService() { 179 if (versioningService == null) { 180 versioningService = Framework.getService(VersioningService.class); 181 } 182 return versioningService; 183 } 184 185 private transient DocumentValidationService validationService; 186 187 protected DocumentValidationService getValidationService() { 188 if (validationService == null) { 189 validationService = Framework.getService(DocumentValidationService.class); 190 } 191 return validationService; 192 } 193 194 /** 195 * Internal method: Gets the current session based on the client session id. 196 * 197 * @return the repository session 198 */ 199 public abstract Session getSession(); 200 201 @Override 202 public DocumentType getDocumentType(String type) { 203 return Framework.getLocalService(SchemaManager.class).getDocumentType(type); 204 } 205 206 protected final void checkPermission(Document doc, String permission) throws DocumentSecurityException { 207 if (isAdministrator()) { 208 return; 209 } 210 if (!hasPermission(doc, permission)) { 211 log.debug("Permission '" + permission + "' is not granted to '" + getPrincipal().getName() 212 + "' on document " + doc.getPath() + " (" + doc.getUUID() + " - " + doc.getType().getName() + ")"); 213 throw new DocumentSecurityException("Privilege '" + permission + "' is not granted to '" 214 + getPrincipal().getName() + "'"); 215 } 216 } 217 218 protected Map<String, Serializable> getContextMapEventInfo(DocumentModel doc) { 219 Map<String, Serializable> options = new HashMap<>(); 220 if (doc != null) { 221 options.putAll(doc.getContextData()); 222 } 223 return options; 224 } 225 226 public DocumentEventContext newEventContext(DocumentModel source) { 227 DocumentEventContext ctx = new DocumentEventContext(this, getPrincipal(), source); 228 ctx.setProperty(CoreEventConstants.REPOSITORY_NAME, getRepositoryName()); 229 ctx.setProperty(CoreEventConstants.SESSION_ID, getSessionId()); 230 return ctx; 231 } 232 233 protected void notifyEvent(String eventId, DocumentModel source, Map<String, Serializable> options, 234 String category, String comment, boolean withLifeCycle, boolean inline) { 235 236 DocumentEventContext ctx = new DocumentEventContext(this, getPrincipal(), source); 237 238 // compatibility with old code (< 5.2.M4) - import info from old event 239 // model 240 if (options != null) { 241 ctx.setProperties(options); 242 } 243 ctx.setProperty(CoreEventConstants.REPOSITORY_NAME, getRepositoryName()); 244 ctx.setProperty(CoreEventConstants.SESSION_ID, getSessionId()); 245 // Document life cycle 246 if (source != null && withLifeCycle) { 247 String currentLifeCycleState = source.getCurrentLifeCycleState(); 248 ctx.setProperty(CoreEventConstants.DOC_LIFE_CYCLE, currentLifeCycleState); 249 } 250 if (comment != null) { 251 ctx.setProperty("comment", comment); 252 } 253 ctx.setProperty("category", category == null ? DocumentEventCategories.EVENT_DOCUMENT_CATEGORY : category); 254 // compat code: mark SAVE event as a commit event 255 Event event = ctx.newEvent(eventId); 256 if (DocumentEventTypes.SESSION_SAVED.equals(eventId)) { 257 event.setIsCommitEvent(true); 258 } 259 if (inline) { 260 event.setInline(true); 261 } 262 // compat code: set isLocal on event if JMS is blocked 263 if (source != null) { 264 Boolean blockJms = (Boolean) source.getContextData("BLOCK_JMS_PRODUCING"); 265 if (blockJms != null && blockJms.booleanValue()) { 266 event.setLocal(true); 267 event.setInline(true); 268 } 269 } 270 Framework.getLocalService(EventService.class).fireEvent(event); 271 } 272 273 /** 274 * Copied from obsolete VersionChangeNotifier. 275 * <p> 276 * Sends change notifications to core event listeners. The event contains info with older document (before version 277 * change) and newer doc (current document). 278 * 279 * @param options additional info to pass to the event 280 */ 281 protected void notifyVersionChange(DocumentModel oldDocument, DocumentModel newDocument, 282 Map<String, Serializable> options) { 283 final Map<String, Serializable> info = new HashMap<>(); 284 if (options != null) { 285 info.putAll(options); 286 } 287 info.put(VersioningChangeNotifier.EVT_INFO_NEW_DOC_KEY, newDocument); 288 info.put(VersioningChangeNotifier.EVT_INFO_OLD_DOC_KEY, oldDocument); 289 notifyEvent(VersioningChangeNotifier.CORE_EVENT_ID_VERSIONING_CHANGE, newDocument, info, 290 DocumentEventCategories.EVENT_CLIENT_NOTIF_CATEGORY, null, false, false); 291 } 292 293 @Override 294 public boolean hasPermission(Principal principal, DocumentRef docRef, String permission) { 295 Document doc = resolveReference(docRef); 296 return hasPermission(principal, doc, permission); 297 } 298 299 protected final boolean hasPermission(Principal principal, Document doc, String permission) { 300 return getSecurityService().checkPermission(doc, principal, permission); 301 } 302 303 @Override 304 public boolean hasPermission(DocumentRef docRef, String permission) { 305 Document doc = resolveReference(docRef); 306 return hasPermission(doc, permission); 307 } 308 309 @Override 310 public Collection<String> filterGrantedPermissions(Principal principal, DocumentRef docRef, Collection<String> permissions) { 311 Document doc = resolveReference(docRef); 312 return getSecurityService().filterGrantedPermissions(doc, principal, permissions); 313 } 314 315 protected final boolean hasPermission(Document doc, String permission) { 316 // TODO: optimize this - usually ACP is already available when calling 317 // this method. 318 // -> cache ACP at securitymanager level or try to reuse the ACP when 319 // it is known 320 return getSecurityService().checkPermission(doc, getPrincipal(), permission); 321 // return doc.getSession().getSecurityManager().checkPermission(doc, 322 // getPrincipal().getName(), permission); 323 } 324 325 protected Document resolveReference(DocumentRef docRef) { 326 if (docRef == null) { 327 throw new IllegalArgumentException("null docRref"); 328 } 329 Object ref = docRef.reference(); 330 if (ref == null) { 331 throw new IllegalArgumentException("null reference"); 332 } 333 int type = docRef.type(); 334 switch (type) { 335 case DocumentRef.ID: 336 return getSession().getDocumentByUUID((String) ref); 337 case DocumentRef.PATH: 338 return getSession().resolvePath((String) ref); 339 case DocumentRef.INSTANCE: 340 return getSession().getDocumentByUUID(((DocumentModel) ref).getId()); 341 default: 342 throw new IllegalArgumentException("Invalid type: " + type); 343 } 344 } 345 346 /** 347 * Gets the document model for the given core document. 348 * 349 * @param doc the document 350 * @return the document model 351 */ 352 protected DocumentModel readModel(Document doc) { 353 return DocumentModelFactory.createDocumentModel(doc, getSessionId(), null); 354 } 355 356 /** 357 * Gets the document model for the given core document, preserving the contextData. 358 * 359 * @param doc the document 360 * @return the document model 361 */ 362 protected DocumentModel readModel(Document doc, DocumentModel docModel) { 363 DocumentModel newModel = readModel(doc); 364 newModel.copyContextData(docModel); 365 return newModel; 366 } 367 368 protected DocumentModel writeModel(Document doc, DocumentModel docModel) { 369 return DocumentModelFactory.writeDocumentModel(docModel, doc); 370 } 371 372 @Override 373 @Deprecated 374 public DocumentModel copy(DocumentRef src, DocumentRef dst, String name, boolean resetLifeCycle) { 375 if (resetLifeCycle) { 376 return copy(src, dst, name, CopyOption.RESET_LIFE_CYCLE); 377 } 378 return copy(src, dst, name); 379 } 380 381 @Override 382 public DocumentModel copy(DocumentRef src, DocumentRef dst, String name, CopyOption... copyOptions) { 383 Document dstDoc = resolveReference(dst); 384 checkPermission(dstDoc, ADD_CHILDREN); 385 386 Document srcDoc = resolveReference(src); 387 if (name == null) { 388 name = srcDoc.getName(); 389 } else { 390 PathRef.checkName(name); 391 } 392 393 Map<String, Serializable> options = new HashMap<>(); 394 395 // add the destination name, destination, resetLifeCycle flag and 396 // source references in 397 // the options of the event 398 options.put(CoreEventConstants.SOURCE_REF, src); 399 options.put(CoreEventConstants.DESTINATION_REF, dst); 400 options.put(CoreEventConstants.DESTINATION_PATH, dstDoc.getPath()); 401 options.put(CoreEventConstants.DESTINATION_NAME, name); 402 options.put(CoreEventConstants.DESTINATION_EXISTS, dstDoc.hasChild(name)); 403 options.put(CoreEventConstants.RESET_LIFECYCLE, CopyOption.isResetLifeCycle(copyOptions)); 404 options.put(CoreEventConstants.RESET_CREATOR, CopyOption.isResetCreator(copyOptions)); 405 DocumentModel srcDocModel = readModel(srcDoc); 406 notifyEvent(DocumentEventTypes.ABOUT_TO_COPY, srcDocModel, options, null, null, true, true); 407 408 name = (String) options.get(CoreEventConstants.DESTINATION_NAME); 409 Document doc = getSession().copy(srcDoc, dstDoc, name); 410 // no need to clear lock, locks table is not copied 411 412 // notify document created by copy 413 DocumentModel docModel = readModel(doc); 414 415 String comment = srcDoc.getRepositoryName() + ':' + src.toString(); 416 notifyEvent(DocumentEventTypes.DOCUMENT_CREATED_BY_COPY, docModel, options, null, comment, true, false); 417 docModel = writeModel(doc, docModel); 418 419 // notify document copied 420 comment = doc.getRepositoryName() + ':' + docModel.getRef().toString(); 421 422 notifyEvent(DocumentEventTypes.DOCUMENT_DUPLICATED, srcDocModel, options, null, comment, true, false); 423 424 return docModel; 425 } 426 427 @Override 428 @Deprecated 429 public List<DocumentModel> copy(List<DocumentRef> src, DocumentRef dst, boolean resetLifeCycle) { 430 if (resetLifeCycle) { 431 return copy(src, dst, CopyOption.RESET_LIFE_CYCLE); 432 } 433 return copy(src, dst); 434 } 435 436 @Override 437 public List<DocumentModel> copy(List<DocumentRef> src, DocumentRef dst, CopyOption... opts) { 438 return src.stream().map(ref -> copy(ref, dst, null, opts)).collect(Collectors.toList()); 439 } 440 441 @Override 442 @Deprecated 443 public DocumentModel copyProxyAsDocument(DocumentRef src, DocumentRef dst, String name, boolean resetLifeCycle) { 444 if (resetLifeCycle) { 445 return copyProxyAsDocument(src, dst, name, CopyOption.RESET_LIFE_CYCLE); 446 } 447 return copyProxyAsDocument(src, dst, name); 448 } 449 450 @Override 451 public DocumentModel copyProxyAsDocument(DocumentRef src, DocumentRef dst, String name, CopyOption... copyOptions) { 452 Document srcDoc = resolveReference(src); 453 if (!srcDoc.isProxy()) { 454 return copy(src, dst, name); 455 } 456 Document dstDoc = resolveReference(dst); 457 checkPermission(dstDoc, WRITE); 458 459 // create a new document using the expanded proxy 460 DocumentModel srcDocModel = readModel(srcDoc); 461 String docName = (name != null) ? name : srcDocModel.getName(); 462 DocumentModel docModel = createDocumentModel(dstDoc.getPath(), docName, srcDocModel.getType()); 463 docModel.copyContent(srcDocModel); 464 notifyEvent(DocumentEventTypes.ABOUT_TO_COPY, srcDocModel, null, null, null, true, true); 465 docModel = createDocument(docModel); 466 Document doc = resolveReference(docModel.getRef()); 467 468 Map<String, Serializable> options = new HashMap<>(); 469 options.put(CoreEventConstants.RESET_LIFECYCLE, CopyOption.isResetLifeCycle(copyOptions)); 470 options.put(CoreEventConstants.RESET_CREATOR, CopyOption.isResetCreator(copyOptions)); 471 // notify document created by copy 472 String comment = srcDoc.getRepositoryName() + ':' + src.toString(); 473 notifyEvent(DocumentEventTypes.DOCUMENT_CREATED_BY_COPY, docModel, options, null, comment, true, false); 474 475 // notify document copied 476 comment = doc.getRepositoryName() + ':' + docModel.getRef().toString(); 477 notifyEvent(DocumentEventTypes.DOCUMENT_DUPLICATED, srcDocModel, options, null, comment, true, false); 478 479 return docModel; 480 } 481 482 @Override 483 @Deprecated 484 public List<DocumentModel> copyProxyAsDocument(List<DocumentRef> src, DocumentRef dst, boolean resetLifeCycle) { 485 if (resetLifeCycle) { 486 return copyProxyAsDocument(src, dst, CopyOption.RESET_LIFE_CYCLE); 487 } 488 return copyProxyAsDocument(src, dst); 489 } 490 491 @Override 492 public List<DocumentModel> copyProxyAsDocument(List<DocumentRef> src, DocumentRef dst, CopyOption... opts) { 493 return src.stream().map(ref -> copyProxyAsDocument(ref, dst, null, opts)).collect(Collectors.toList()); 494 } 495 496 @Override 497 public DocumentModel move(DocumentRef src, DocumentRef dst, String name) { 498 Document srcDoc = resolveReference(src); 499 Document dstDoc; 500 if (dst == null) { 501 // rename 502 dstDoc = srcDoc.getParent(); 503 checkPermission(dstDoc, WRITE_PROPERTIES); 504 } else { 505 dstDoc = resolveReference(dst); 506 checkPermission(dstDoc, ADD_CHILDREN); 507 checkPermission(srcDoc.getParent(), REMOVE_CHILDREN); 508 checkPermission(srcDoc, REMOVE); 509 } 510 511 DocumentModel srcDocModel = readModel(srcDoc); 512 String originalName = srcDocModel.getName(); 513 if (name == null) { 514 name = srcDocModel.getName(); 515 } else { 516 PathRef.checkName(name); 517 } 518 Map<String, Serializable> options = getContextMapEventInfo(srcDocModel); 519 // add the destination name, destination and source references in 520 // the options of the event 521 options.put(CoreEventConstants.SOURCE_REF, src); 522 options.put(CoreEventConstants.DESTINATION_REF, dst); 523 options.put(CoreEventConstants.DESTINATION_PATH, dstDoc.getPath()); 524 options.put(CoreEventConstants.DESTINATION_NAME, name); 525 options.put(CoreEventConstants.DESTINATION_EXISTS, dstDoc.hasChild(name)); 526 527 notifyEvent(DocumentEventTypes.ABOUT_TO_MOVE, srcDocModel, options, null, null, true, true); 528 529 name = (String) options.get(CoreEventConstants.DESTINATION_NAME); 530 531 if (!originalName.equals(name)) { 532 options.put(CoreEventConstants.ORIGINAL_NAME, originalName); 533 } 534 535 String comment = srcDoc.getRepositoryName() + ':' + srcDoc.getParent().getUUID(); 536 537 Document doc = getSession().move(srcDoc, dstDoc, name); 538 539 // notify document moved 540 DocumentModel docModel = readModel(doc); 541 options.put(CoreEventConstants.PARENT_PATH, srcDocModel.getParentRef()); 542 notifyEvent(DocumentEventTypes.DOCUMENT_MOVED, docModel, options, null, comment, true, false); 543 544 return docModel; 545 } 546 547 @Override 548 public void move(List<DocumentRef> src, DocumentRef dst) { 549 for (DocumentRef ref : src) { 550 move(ref, dst, null); 551 } 552 } 553 554 @Override 555 public ACP getACP(DocumentRef docRef) { 556 Document doc = resolveReference(docRef); 557 checkPermission(doc, READ_SECURITY); 558 return getSession().getMergedACP(doc); 559 } 560 561 @Override 562 public void setACP(DocumentRef docRef, ACP newAcp, boolean overwrite) { 563 Document doc = resolveReference(docRef); 564 checkPermission(doc, WRITE_SECURITY); 565 566 setACP(doc, newAcp, overwrite, null); 567 } 568 569 protected void setACP(Document doc, ACP newAcp, boolean overwrite, Map<String, Serializable> options) { 570 DocumentModel docModel = readModel(doc); 571 if (options == null) { 572 options = new HashMap<>(); 573 } 574 options.put(CoreEventConstants.OLD_ACP, docModel.getACP().clone()); 575 options.put(CoreEventConstants.NEW_ACP, newAcp); 576 577 notifyEvent(DocumentEventTypes.BEFORE_DOC_SECU_UPDATE, docModel, options, null, null, true, true); 578 getSession().setACP(doc, newAcp, overwrite); 579 docModel = readModel(doc); 580 options.put(CoreEventConstants.NEW_ACP, newAcp.clone()); 581 notifyEvent(DocumentEventTypes.DOCUMENT_SECURITY_UPDATED, docModel, options, null, null, true, false); 582 } 583 584 @Override 585 public void replaceACE(DocumentRef docRef, String aclName, ACE oldACE, ACE newACE) { 586 Document doc = resolveReference(docRef); 587 checkPermission(doc, WRITE_SECURITY); 588 589 ACP acp = getACP(docRef); 590 if (acp.replaceACE(aclName, oldACE, newACE)) { 591 Map<String, Serializable> options = new HashMap<>(); 592 options.put(OLD_ACE, oldACE); 593 options.put(NEW_ACE, newACE); 594 options.put(CHANGED_ACL_NAME, aclName); 595 setACP(doc, acp, true, options); 596 } 597 } 598 599 @Override 600 public boolean isNegativeAclAllowed() { 601 return getSession().isNegativeAclAllowed(); 602 } 603 604 @Override 605 public void cancel() { 606 // nothing 607 } 608 609 private DocumentModel createDocumentModelFromTypeName(String typeName, Map<String, Serializable> options) { 610 SchemaManager schemaManager = Framework.getLocalService(SchemaManager.class); 611 DocumentType docType = schemaManager.getDocumentType(typeName); 612 if (docType == null) { 613 throw new IllegalArgumentException(typeName + " is not a registered core type"); 614 } 615 DocumentModel docModel = DocumentModelFactory.createDocumentModel(getSessionId(), docType); 616 if (options == null) { 617 options = new HashMap<>(); 618 } 619 // do not forward this event on the JMS Bus 620 options.put("BLOCK_JMS_PRODUCING", true); 621 notifyEvent(DocumentEventTypes.EMPTY_DOCUMENTMODEL_CREATED, docModel, options, null, null, false, true); 622 return docModel; 623 } 624 625 @Override 626 public DocumentModel createDocumentModel(String typeName) { 627 Map<String, Serializable> options = new HashMap<>(); 628 return createDocumentModelFromTypeName(typeName, options); 629 } 630 631 @Override 632 public DocumentModel createDocumentModel(String parentPath, String name, String typeName) { 633 Map<String, Serializable> options = new HashMap<>(); 634 options.put(CoreEventConstants.PARENT_PATH, parentPath); 635 options.put(CoreEventConstants.DOCUMENT_MODEL_ID, name); 636 options.put(CoreEventConstants.DESTINATION_NAME, name); 637 DocumentModel model = createDocumentModelFromTypeName(typeName, options); 638 model.setPathInfo(parentPath, name); 639 return model; 640 } 641 642 @Override 643 public DocumentModel createDocumentModel(String typeName, Map<String, Object> options) { 644 645 Map<String, Serializable> serializableOptions = new HashMap<>(); 646 647 for (Entry<String, Object> entry : options.entrySet()) { 648 serializableOptions.put(entry.getKey(), (Serializable) entry.getValue()); 649 } 650 return createDocumentModelFromTypeName(typeName, serializableOptions); 651 } 652 653 @Override 654 public DocumentModel createDocument(DocumentModel docModel) { 655 656 // start by removing disallowed characters 657 CharacterFilteringService charFilteringService = Framework.getService(CharacterFilteringService.class); 658 charFilteringService.filter(docModel); 659 660 if (docModel.getSessionId() == null) { 661 // docModel was created using constructor instead of CoreSession.createDocumentModel 662 docModel.attach(getSessionId()); 663 } 664 String typeName = docModel.getType(); 665 DocumentRef parentRef = docModel.getParentRef(); 666 if (typeName == null) { 667 throw new NullPointerException("null typeName"); 668 } 669 if (parentRef == null && !isAdministrator()) { 670 throw new NuxeoException("Only Administrators can create placeless documents"); 671 } 672 String childName = docModel.getName(); 673 Map<String, Serializable> options = getContextMapEventInfo(docModel); 674 675 // document validation 676 if (getValidationService().isActivated(DocumentValidationService.CTX_CREATEDOC, options)) { 677 DocumentValidationReport report = getValidationService().validate(docModel, true); 678 if (report.hasError()) { 679 throw new DocumentValidationException(report); 680 } 681 } 682 683 Document folder = fillCreateOptions(parentRef, childName, options); 684 685 // get initial life cycle state info 686 String initialLifecycleState = null; 687 Object lifecycleStateInfo = docModel.getContextData(LifeCycleConstants.INITIAL_LIFECYCLE_STATE_OPTION_NAME); 688 if (lifecycleStateInfo instanceof String) { 689 initialLifecycleState = (String) lifecycleStateInfo; 690 } 691 notifyEvent(DocumentEventTypes.ABOUT_TO_CREATE, docModel, options, null, null, false, true); // no lifecycle 692 // yet 693 childName = (String) options.get(CoreEventConstants.DESTINATION_NAME); 694 Document doc = folder.addChild(childName, typeName); 695 696 // update facets too since some of them may be dynamic 697 for (String facetName : docModel.getFacets()) { 698 if (!doc.getAllFacets().contains(facetName) && !FacetNames.IMMUTABLE.equals(facetName)) { 699 doc.addFacet(facetName); 700 } 701 } 702 703 // init document life cycle 704 getLifeCycleService().initialize(doc, initialLifecycleState); 705 706 // init document with data from doc model 707 docModel = writeModel(doc, docModel); 708 709 if (!Boolean.TRUE.equals(docModel.getContextData(VersioningService.SKIP_VERSIONING))) { 710 // during remote publishing we want to skip versioning 711 // to avoid overwriting the version number 712 getVersioningService().doPostCreate(doc, options); 713 } 714 715 // post-create event 716 docModel = readModel(doc, docModel); 717 // compute auto versioning 718 // no need to fire event, as we use DocumentModel API it's already done 719 // we don't rely on SKIP_VERSIONING because automatic versioning in saveDocument as the same behavior - and it 720 // doesn't erase initial version as it's the case to avoid when setting Skip_VERSIONING 721 getVersioningService().doAutomaticVersioning(null, docModel, false); 722 notifyEvent(DocumentEventTypes.DOCUMENT_CREATED, docModel, options, null, null, true, false); 723 docModel = writeModel(doc, docModel); 724 725 createDocumentCount.inc(); 726 return docModel; 727 } 728 729 private transient LifeCycleService lifeCycleService; 730 731 private LifeCycleService getLifeCycleService() { 732 if (lifeCycleService == null) { 733 lifeCycleService = NXCore.getLifeCycleService(); 734 } 735 return lifeCycleService; 736 } 737 738 protected Document fillCreateOptions(DocumentRef parentRef, String childName, Map<String, Serializable> options) 739 throws DocumentSecurityException { 740 Document folder; 741 if (parentRef == null || EMPTY_PATH.equals(parentRef)) { 742 folder = getSession().getNullDocument(); 743 options.put(CoreEventConstants.DESTINATION_REF, null); 744 options.put(CoreEventConstants.DESTINATION_PATH, null); 745 options.put(CoreEventConstants.DESTINATION_NAME, childName); 746 options.put(CoreEventConstants.DESTINATION_EXISTS, false); 747 } else { 748 folder = resolveReference(parentRef); 749 checkPermission(folder, ADD_CHILDREN); 750 options.put(CoreEventConstants.DESTINATION_REF, parentRef); 751 options.put(CoreEventConstants.DESTINATION_PATH, folder.getPath()); 752 options.put(CoreEventConstants.DESTINATION_NAME, childName); 753 if (Boolean.TRUE.equals(options.get(CoreSession.SKIP_DESTINATION_CHECK_ON_CREATE))) { 754 options.put(CoreEventConstants.DESTINATION_EXISTS, false); 755 } else { 756 options.put(CoreEventConstants.DESTINATION_EXISTS, folder.hasChild(childName)); 757 } 758 } 759 return folder; 760 } 761 762 @Override 763 public void importDocuments(List<DocumentModel> docModels) { 764 docModels.forEach(this::importDocument); 765 } 766 767 protected static final PathRef EMPTY_PATH = new PathRef(""); 768 769 protected void importDocument(DocumentModel docModel) { 770 if (!isAdministrator()) { 771 throw new DocumentSecurityException("Only Administrator can import"); 772 } 773 String name = docModel.getName(); 774 if (name == null || name.length() == 0) { 775 throw new IllegalArgumentException("Invalid empty name"); 776 } 777 String typeName = docModel.getType(); 778 if (typeName == null || typeName.length() == 0) { 779 throw new IllegalArgumentException("Invalid empty type"); 780 } 781 String id = docModel.getId(); 782 if (id == null || id.length() == 0) { 783 throw new IllegalArgumentException("Invalid empty id"); 784 } 785 786 DocumentRef parentRef = docModel.getParentRef(); 787 Map<String, Serializable> props = getContextMapEventInfo(docModel); 788 789 // document validation 790 if (getValidationService().isActivated(DocumentValidationService.CTX_IMPORTDOC, props)) { 791 DocumentValidationReport report = getValidationService().validate(docModel, true); 792 if (report.hasError()) { 793 throw new DocumentValidationException(report); 794 } 795 } 796 797 if (parentRef != null && EMPTY_PATH.equals(parentRef)) { 798 parentRef = null; 799 } 800 Document parent = fillCreateOptions(parentRef, name, props); 801 notifyEvent(DocumentEventTypes.ABOUT_TO_IMPORT, docModel, props, null, null, false, true); 802 name = (String) props.get(CoreEventConstants.DESTINATION_NAME); 803 804 // create the document 805 Document doc = getSession().importDocument(id, parentRef == null ? null : parent, name, typeName, props); 806 807 if (typeName.equals(CoreSession.IMPORT_PROXY_TYPE)) { 808 // just reread the final document 809 docModel = readModel(doc); 810 } else { 811 // init document with data from doc model 812 docModel = writeModel(doc, docModel); 813 } 814 815 // send an event about the import 816 notifyEvent(DocumentEventTypes.DOCUMENT_IMPORTED, docModel, null, null, null, true, false); 817 } 818 819 @Override 820 public DocumentModel[] createDocument(DocumentModel[] docModels) { 821 DocumentModel[] models = new DocumentModel[docModels.length]; 822 int i = 0; 823 // TODO: optimize this (do not call at each iteration createDocument()) 824 for (DocumentModel docModel : docModels) { 825 models[i++] = createDocument(docModel); 826 } 827 return models; 828 } 829 830 @Override 831 public boolean exists(DocumentRef docRef) { 832 try { 833 Document doc = resolveReference(docRef); 834 return hasPermission(doc, BROWSE); 835 } catch (DocumentNotFoundException e) { 836 return false; 837 } 838 } 839 840 @Override 841 public DocumentModel getChild(DocumentRef parent, String name) { 842 Document doc = resolveReference(parent); 843 checkPermission(doc, READ_CHILDREN); 844 Document child = doc.getChild(name); 845 checkPermission(child, READ); 846 return readModel(child); 847 } 848 849 @Override 850 public boolean hasChild(DocumentRef parent, String name) { 851 Document doc = resolveReference(parent); 852 checkPermission(doc, READ_CHILDREN); 853 return doc.hasChild(name); 854 } 855 856 @Override 857 public DocumentModelList getChildren(DocumentRef parent) { 858 return getChildren(parent, null, READ, null, null); 859 } 860 861 @Override 862 public DocumentModelList getChildren(DocumentRef parent, String type) { 863 return getChildren(parent, type, READ, null, null); 864 } 865 866 @Override 867 public DocumentModelList getChildren(DocumentRef parent, String type, String perm) { 868 return getChildren(parent, type, perm, null, null); 869 } 870 871 @Override 872 public DocumentModelList getChildren(DocumentRef parent, String type, Filter filter, Sorter sorter) { 873 return getChildren(parent, type, null, filter, sorter); 874 } 875 876 @Override 877 public DocumentModelList getChildren(DocumentRef parent, String type, String perm, Filter filter, Sorter sorter) { 878 if (perm == null) { 879 perm = READ; 880 } 881 Document doc = resolveReference(parent); 882 checkPermission(doc, READ_CHILDREN); 883 DocumentModelList docs = new DocumentModelListImpl(); 884 for (Document child : doc.getChildren()) { 885 if (hasPermission(child, perm)) { 886 if (child.getType() != null && (type == null || type.equals(child.getType().getName()))) { 887 DocumentModel childModel = readModel(child); 888 if (filter == null || filter.accept(childModel)) { 889 docs.add(childModel); 890 } 891 } 892 } 893 } 894 if (sorter != null) { 895 Collections.sort(docs, sorter); 896 } 897 return docs; 898 } 899 900 @Override 901 public List<DocumentRef> getChildrenRefs(DocumentRef parentRef, String perm) { 902 if (perm != null) { 903 // XXX TODO 904 throw new NullPointerException("perm != null not implemented"); 905 } 906 Document parent = resolveReference(parentRef); 907 checkPermission(parent, READ_CHILDREN); 908 List<String> ids = parent.getChildrenIds(); 909 List<DocumentRef> refs = new ArrayList<>(ids.size()); 910 for (String id : ids) { 911 refs.add(new IdRef(id)); 912 } 913 return refs; 914 } 915 916 @Override 917 public DocumentModelIterator getChildrenIterator(DocumentRef parent) { 918 return getChildrenIterator(parent, null, null, null); 919 } 920 921 @Override 922 public DocumentModelIterator getChildrenIterator(DocumentRef parent, String type) { 923 return getChildrenIterator(parent, type, null, null); 924 } 925 926 @Override 927 public DocumentModelIterator getChildrenIterator(DocumentRef parent, String type, String perm, Filter filter) { 928 // perm unused, kept for API compat 929 return new DocumentModelChildrenIterator(this, parent, type, filter); 930 } 931 932 @Override 933 public DocumentModel getDocument(DocumentRef docRef) { 934 Document doc = resolveReference(docRef); 935 checkPermission(doc, READ); 936 return readModel(doc); 937 } 938 939 @Override 940 public DocumentModelList getDocuments(DocumentRef[] docRefs) { 941 List<DocumentModel> docs = new ArrayList<>(docRefs.length); 942 for (DocumentRef docRef : docRefs) { 943 Document doc; 944 try { 945 doc = resolveReference(docRef); 946 checkPermission(doc, READ); 947 } catch (DocumentSecurityException e) { 948 // no permission 949 continue; 950 } 951 docs.add(readModel(doc)); 952 } 953 return new DocumentModelListImpl(docs); 954 } 955 956 @Override 957 public DocumentModelList getFiles(DocumentRef parent) { 958 Document doc = resolveReference(parent); 959 checkPermission(doc, READ_CHILDREN); 960 DocumentModelList docs = new DocumentModelListImpl(); 961 for (Document child : doc.getChildren()) { 962 if (!child.isFolder() && hasPermission(child, READ)) { 963 docs.add(readModel(child)); 964 } 965 } 966 return docs; 967 } 968 969 @Override 970 public DocumentModelList getFiles(DocumentRef parent, Filter filter, Sorter sorter) { 971 Document doc = resolveReference(parent); 972 checkPermission(doc, READ_CHILDREN); 973 DocumentModelList docs = new DocumentModelListImpl(); 974 for (Document child : doc.getChildren()) { 975 if (!child.isFolder() && hasPermission(child, READ)) { 976 DocumentModel docModel = readModel(doc); 977 if (filter == null || filter.accept(docModel)) { 978 docs.add(readModel(child)); 979 } 980 } 981 } 982 if (sorter != null) { 983 Collections.sort(docs, sorter); 984 } 985 return docs; 986 } 987 988 @Override 989 public DocumentModelList getFolders(DocumentRef parent) { 990 Document doc = resolveReference(parent); 991 checkPermission(doc, READ_CHILDREN); 992 DocumentModelList docs = new DocumentModelListImpl(); 993 for (Document child : doc.getChildren()) { 994 if (child.isFolder() && hasPermission(child, READ)) { 995 docs.add(readModel(child)); 996 } 997 } 998 return docs; 999 } 1000 1001 @Override 1002 public DocumentModelList getFolders(DocumentRef parent, Filter filter, Sorter sorter) { 1003 Document doc = resolveReference(parent); 1004 checkPermission(doc, READ_CHILDREN); 1005 DocumentModelList docs = new DocumentModelListImpl(); 1006 for (Document child : doc.getChildren()) { 1007 if (child.isFolder() && hasPermission(child, READ)) { 1008 DocumentModel childModel = readModel(child); 1009 if (filter == null || filter.accept(childModel)) { 1010 docs.add(childModel); 1011 } 1012 } 1013 } 1014 if (sorter != null) { 1015 Collections.sort(docs, sorter); 1016 } 1017 return docs; 1018 } 1019 1020 @Override 1021 public DocumentRef getParentDocumentRef(DocumentRef docRef) { 1022 final Document doc = resolveReference(docRef); 1023 Document parentDoc = doc.getParent(); 1024 return parentDoc != null ? new IdRef(parentDoc.getUUID()) : null; 1025 } 1026 1027 @Override 1028 public DocumentModel getParentDocument(DocumentRef docRef) { 1029 Document doc = resolveReference(docRef); 1030 Document parentDoc = doc.getParent(); 1031 if (parentDoc == null) { 1032 return null; 1033 } 1034 if (!hasPermission(parentDoc, READ)) { 1035 throw new DocumentSecurityException("Privilege READ is not granted to " + getPrincipal().getName()); 1036 } 1037 return readModel(parentDoc); 1038 } 1039 1040 @Override 1041 public List<DocumentModel> getParentDocuments(final DocumentRef docRef) { 1042 1043 if (null == docRef) { 1044 throw new IllegalArgumentException("null docRef"); 1045 } 1046 1047 final List<DocumentModel> docsList = new ArrayList<>(); 1048 Document doc = resolveReference(docRef); 1049 while (doc != null && !"/".equals(doc.getPath())) { 1050 // XXX OG: shouldn't we check BROWSE and READ_PROPERTIES 1051 // instead? 1052 if (!hasPermission(doc, READ)) { 1053 break; 1054 } 1055 docsList.add(readModel(doc)); 1056 doc = doc.getParent(); 1057 } 1058 Collections.reverse(docsList); 1059 return docsList; 1060 } 1061 1062 @Override 1063 public DocumentModel getRootDocument() { 1064 return readModel(getSession().getRootDocument()); 1065 } 1066 1067 @Override 1068 public boolean hasChildren(DocumentRef docRef) { 1069 // TODO: validate permission check with td 1070 Document doc = resolveReference(docRef); 1071 checkPermission(doc, BROWSE); 1072 return doc.hasChildren(); 1073 } 1074 1075 @Override 1076 public DocumentModelList query(String query) { 1077 return query(query, null, 0, 0, false); 1078 } 1079 1080 @Override 1081 public DocumentModelList query(String query, int max) { 1082 return query(query, null, max, 0, false); 1083 } 1084 1085 @Override 1086 public DocumentModelList query(String query, Filter filter) { 1087 return query(query, filter, 0, 0, false); 1088 } 1089 1090 @Override 1091 public DocumentModelList query(String query, Filter filter, int max) { 1092 return query(query, filter, max, 0, false); 1093 } 1094 1095 @Override 1096 public DocumentModelList query(String query, Filter filter, long limit, long offset, boolean countTotal) { 1097 return query(query, NXQL.NXQL, filter, limit, offset, countTotal); 1098 } 1099 1100 @Override 1101 public DocumentModelList query(String query, String queryType, Filter filter, long limit, long offset, 1102 boolean countTotal) { 1103 long countUpTo; 1104 if (!countTotal) { 1105 countUpTo = 0; 1106 } else { 1107 if (isLimitedResults()) { 1108 countUpTo = getMaxResults(); 1109 } else { 1110 countUpTo = -1; 1111 } 1112 } 1113 return query(query, queryType, filter, limit, offset, countUpTo); 1114 } 1115 1116 protected long getMaxResults() { 1117 if (maxResults == null) { 1118 maxResults = Long.parseLong(Framework.getProperty(MAX_RESULTS_PROPERTY, DEFAULT_MAX_RESULTS)); 1119 } 1120 return maxResults; 1121 } 1122 1123 protected boolean isLimitedResults() { 1124 if (limitedResults == null) { 1125 limitedResults = Boolean.parseBoolean(Framework.getProperty(LIMIT_RESULTS_PROPERTY)); 1126 } 1127 return limitedResults; 1128 } 1129 1130 protected void setMaxResults(long maxResults) { 1131 this.maxResults = maxResults; 1132 } 1133 1134 protected void setLimitedResults(boolean limitedResults) { 1135 this.limitedResults = limitedResults; 1136 } 1137 1138 @Override 1139 public DocumentModelList query(String query, Filter filter, long limit, long offset, long countUpTo) { 1140 return query(query, NXQL.NXQL, filter, limit, offset, countUpTo); 1141 } 1142 1143 @Override 1144 public DocumentModelList query(String query, String queryType, Filter filter, long limit, long offset, 1145 long countUpTo) { 1146 SecurityService securityService = getSecurityService(); 1147 Principal principal = getPrincipal(); 1148 try { 1149 String permission = BROWSE; 1150 String repoName = getRepositoryName(); 1151 boolean postFilterPolicies = !securityService.arePoliciesExpressibleInQuery(repoName); 1152 boolean postFilterFilter = filter != null && !(filter instanceof FacetFilter); 1153 boolean postFilter = postFilterPolicies || postFilterFilter; 1154 String[] principals; 1155 if (isAdministrator()) { 1156 principals = null; // means: no security check needed 1157 } else { 1158 principals = SecurityService.getPrincipalsToCheck(principal); 1159 } 1160 String[] permissions = securityService.getPermissionsToCheck(permission); 1161 QueryFilter queryFilter = new QueryFilter(principal, principals, permissions, 1162 filter instanceof FacetFilter ? (FacetFilter) filter : null, 1163 securityService.getPoliciesQueryTransformers(repoName), postFilter ? 0 : limit, postFilter ? 0 1164 : offset); 1165 1166 // get document list with total size 1167 PartialList<Document> pl = getSession().query(query, queryType, queryFilter, postFilter ? -1 : countUpTo); 1168 // convert to DocumentModelList 1169 DocumentModelListImpl dms = new DocumentModelListImpl(pl.list.size()); 1170 dms.setTotalSize(pl.totalSize); 1171 for (Document doc : pl.list) { 1172 dms.add(readModel(doc)); 1173 } 1174 1175 if (!postFilter) { 1176 // the backend has done all the needed filtering 1177 return dms; 1178 } 1179 1180 // post-filter the results "by hand", the backend couldn't do it 1181 long start = limit == 0 || offset < 0 ? 0 : offset; 1182 long stop = start + (limit == 0 ? dms.size() : limit); 1183 int n = 0; 1184 DocumentModelListImpl docs = new DocumentModelListImpl(); 1185 for (DocumentModel model : dms) { 1186 if (postFilterPolicies) { 1187 if (!hasPermission(model.getRef(), permission)) { 1188 continue; 1189 } 1190 } 1191 if (postFilterFilter) { 1192 if (!filter.accept(model)) { 1193 continue; 1194 } 1195 } 1196 if (n < start) { 1197 n++; 1198 continue; 1199 } 1200 if (n >= stop) { 1201 if (countUpTo == 0) { 1202 // can break early 1203 break; 1204 } 1205 n++; 1206 continue; 1207 } 1208 n++; 1209 docs.add(model); 1210 } 1211 if (countUpTo != 0) { 1212 docs.setTotalSize(n); 1213 } 1214 return docs; 1215 } catch (QueryParseException e) { 1216 e.addInfo("Failed to execute query: " + query); 1217 throw e; 1218 } 1219 } 1220 1221 @Override 1222 public IterableQueryResult queryAndFetch(String query, String queryType, Object... params) { 1223 return queryAndFetch(query, queryType, false, params); 1224 } 1225 1226 @Override 1227 public IterableQueryResult queryAndFetch(String query, String queryType, boolean distinctDocuments, 1228 Object... params) { 1229 try { 1230 SecurityService securityService = getSecurityService(); 1231 Principal principal = getPrincipal(); 1232 String[] principals; 1233 if (isAdministrator()) { 1234 principals = null; // means: no security check needed 1235 } else { 1236 principals = SecurityService.getPrincipalsToCheck(principal); 1237 } 1238 String permission = BROWSE; 1239 String[] permissions = securityService.getPermissionsToCheck(permission); 1240 Collection<Transformer> transformers; 1241 if (NXQL.NXQL.equals(queryType)) { 1242 String repoName = getRepositoryName(); 1243 transformers = securityService.getPoliciesQueryTransformers(repoName); 1244 } else { 1245 transformers = Collections.emptyList(); 1246 } 1247 QueryFilter queryFilter = new QueryFilter(principal, principals, permissions, null, transformers, 0, 0); 1248 IterableQueryResult result = getSession().queryAndFetch(query, queryType, queryFilter, distinctDocuments, 1249 params); 1250 return result; 1251 } catch (QueryParseException e) { 1252 e.addInfo("Failed to execute query: " + queryType + ": " + query); 1253 throw e; 1254 } 1255 } 1256 1257 @Override 1258 public ScrollResult scroll(String query, int batchSize, int keepAliveSeconds) { 1259 if (!isAdministrator()) { 1260 throw new NuxeoException("Only Administrators can scroll"); 1261 } 1262 return getSession().scroll(query, batchSize, keepAliveSeconds); 1263 } 1264 1265 @Override 1266 public ScrollResult scroll(String scrollId) { 1267 if (!isAdministrator()) { 1268 throw new NuxeoException("Only Administrators can scroll"); 1269 } 1270 return getSession().scroll(scrollId); 1271 } 1272 1273 @Override 1274 public void removeChildren(DocumentRef docRef) { 1275 // TODO: check req permissions with td 1276 Document doc = resolveReference(docRef); 1277 checkPermission(doc, REMOVE_CHILDREN); 1278 List<Document> children = doc.getChildren(); 1279 // remove proxies first, otherwise they could become dangling 1280 for (Document child : children) { 1281 if (child.isProxy()) { 1282 if (hasPermission(child, REMOVE)) { 1283 removeNotifyOneDoc(child); 1284 } 1285 } 1286 } 1287 // then remove regular docs or versions, both of which could be proxies targets 1288 for (Document child : children) { 1289 if (!child.isProxy()) { 1290 if (hasPermission(child, REMOVE)) { 1291 removeNotifyOneDoc(child); 1292 } 1293 } 1294 } 1295 } 1296 1297 @Override 1298 public boolean canRemoveDocument(DocumentRef docRef) { 1299 Document doc = resolveReference(docRef); 1300 return canRemoveDocument(doc) == null; 1301 } 1302 1303 /** 1304 * Checks if a document can be removed, and returns a failure reason if not. 1305 */ 1306 protected String canRemoveDocument(Document doc) { 1307 // TODO must also check for proxies on live docs 1308 if (doc.isVersion()) { 1309 // TODO a hasProxies method would be more efficient 1310 Collection<Document> proxies = getSession().getProxies(doc, null); 1311 if (!proxies.isEmpty()) { 1312 return "Proxy " + proxies.iterator().next().getUUID() + " targets version " + doc.getUUID(); 1313 } 1314 // find a working document to check security 1315 Document working = doc.getSourceDocument(); 1316 if (working != null) { 1317 Document baseVersion = working.getBaseVersion(); 1318 if (baseVersion != null && !baseVersion.isCheckedOut() && baseVersion.getUUID().equals(doc.getUUID())) { 1319 return "Working copy " + working.getUUID() + " is checked in with base version " + doc.getUUID(); 1320 } 1321 return hasPermission(working, WRITE_VERSION) ? null : "Missing permission '" + WRITE_VERSION 1322 + "' on working copy " + working.getUUID(); 1323 } else { 1324 // no working document, only admins can remove 1325 return isAdministrator() ? null : "No working copy and not an Administrator"; 1326 } 1327 } else { 1328 if (isAdministrator()) { 1329 return null; // ok 1330 } 1331 if (!hasPermission(doc, REMOVE)) { 1332 return "Missing permission '" + REMOVE + "' on document " + doc.getUUID(); 1333 } 1334 Document parent = doc.getParent(); 1335 if (parent == null) { 1336 return null; // ok 1337 } 1338 return hasPermission(parent, REMOVE_CHILDREN) ? null : "Missing permission '" + REMOVE_CHILDREN 1339 + "' on parent document " + parent.getUUID(); 1340 } 1341 } 1342 1343 @Override 1344 public void removeDocument(DocumentRef docRef) { 1345 Document doc = resolveReference(docRef); 1346 removeDocument(doc); 1347 } 1348 1349 protected void removeDocument(Document doc) { 1350 try { 1351 String reason = canRemoveDocument(doc); 1352 if (reason != null) { 1353 throw new DocumentSecurityException("Permission denied: cannot remove document " + doc.getUUID() + ", " 1354 + reason); 1355 } 1356 removeNotifyOneDoc(doc); 1357 1358 } catch (ConcurrentUpdateException e) { 1359 e.addInfo("Failed to remove document " + doc.getUUID()); 1360 throw e; 1361 } 1362 deleteDocumentCount.inc(); 1363 } 1364 1365 protected void removeNotifyOneDoc(Document doc) { 1366 // XXX notify with options if needed 1367 DocumentModel docModel = readModel(doc); 1368 Map<String, Serializable> options = new HashMap<>(); 1369 if (docModel != null) { 1370 options.put("docTitle", docModel.getTitle()); 1371 } 1372 String versionLabel = ""; 1373 Document sourceDoc = null; 1374 // notify different events depending on wether the document is a 1375 // version or not 1376 if (!doc.isVersion()) { 1377 notifyEvent(DocumentEventTypes.ABOUT_TO_REMOVE, docModel, options, null, null, true, true); 1378 CoreService coreService = Framework.getLocalService(CoreService.class); 1379 coreService.getVersionRemovalPolicy().removeVersions(getSession(), doc, this); 1380 } else { 1381 versionLabel = docModel.getVersionLabel(); 1382 sourceDoc = doc.getSourceDocument(); 1383 notifyEvent(DocumentEventTypes.ABOUT_TO_REMOVE_VERSION, docModel, options, null, null, true, true); 1384 1385 } 1386 doc.remove(); 1387 if (doc.isVersion()) { 1388 if (sourceDoc != null) { 1389 DocumentModel sourceDocModel = readModel(sourceDoc); 1390 if (sourceDocModel != null) { 1391 options.put("comment", versionLabel); // to be used by 1392 // audit 1393 // service 1394 notifyEvent(DocumentEventTypes.VERSION_REMOVED, sourceDocModel, options, null, null, false, false); 1395 options.remove("comment"); 1396 } 1397 options.put("docSource", sourceDoc.getUUID()); 1398 } 1399 } 1400 notifyEvent(DocumentEventTypes.DOCUMENT_REMOVED, docModel, options, null, null, false, false); 1401 } 1402 1403 /** 1404 * Implementation uses the fact that the lexicographic ordering of paths is a refinement of the "contains" partial 1405 * ordering. 1406 */ 1407 @Override 1408 public void removeDocuments(DocumentRef[] docRefs) { 1409 Document[] docs = new Document[docRefs.length]; 1410 1411 for (int i = 0; i < docs.length; i++) { 1412 docs[i] = resolveReference(docRefs[i]); 1413 } 1414 // TODO OPTIM: it's not guaranteed that getPath is cheap and 1415 // we call it a lot. Should use an object for pairs (document, path) 1416 // to call it just once per doc. 1417 Arrays.sort(docs, pathComparator); // nulls first 1418 String[] paths = new String[docs.length]; 1419 for (int i = 0; i < docs.length; i++) { 1420 paths[i] = docs[i].getPath(); 1421 } 1422 String lastRemovedWithSlash = "\u0000"; 1423 for (int i = 0; i < docs.length; i++) { 1424 String path = paths[i]; 1425 if (i == 0 || path == null || !path.startsWith(lastRemovedWithSlash)) { 1426 removeDocument(docs[i]); 1427 if (path != null) { 1428 lastRemovedWithSlash = path + "/"; 1429 } 1430 } 1431 } 1432 } 1433 1434 @Override 1435 public void save() { 1436 try { 1437 final Map<String, Serializable> options = new HashMap<>(); 1438 getSession().save(); 1439 notifyEvent(DocumentEventTypes.SESSION_SAVED, null, options, null, null, true, false); 1440 } catch (ConcurrentUpdateException e) { 1441 e.addInfo("Failed to save session"); 1442 throw e; 1443 } 1444 } 1445 1446 @Override 1447 public DocumentModel saveDocument(DocumentModel docModel) { 1448 if (docModel.getRef() == null) { 1449 throw new IllegalArgumentException(String.format("cannot save document '%s' with null reference: " 1450 + "document has probably not yet been created " + "in the repository with " 1451 + "'CoreSession.createDocument(docModel)'", docModel.getTitle())); 1452 } 1453 1454 Document doc = resolveReference(docModel.getRef()); 1455 checkPermission(doc, WRITE_PROPERTIES); 1456 1457 Map<String, Serializable> options = getContextMapEventInfo(docModel); 1458 1459 boolean dirty = docModel.isDirty(); 1460 1461 if (dirty) { 1462 // document validation 1463 if (getValidationService().isActivated(DocumentValidationService.CTX_SAVEDOC, options)) { 1464 DocumentValidationReport report = getValidationService().validate(docModel, true); 1465 if (report.hasError()) { 1466 throw new DocumentValidationException(report); 1467 } 1468 } 1469 // remove disallowed characters 1470 CharacterFilteringService charFilteringService = Framework.getService(CharacterFilteringService.class); 1471 charFilteringService.filter(docModel); 1472 } 1473 1474 DocumentModel previousDocModel = readModel(doc); 1475 // load previous data for versioning purpose, we want previous document filled with previous value which could 1476 // not be the case if access to property value is done after the update 1477 Arrays.asList(previousDocModel.getSchemas()).forEach(previousDocModel::getProperties); 1478 options.put(CoreEventConstants.PREVIOUS_DOCUMENT_MODEL, previousDocModel); 1479 // regular event, last chance to modify docModel 1480 options.put(CoreEventConstants.DESTINATION_NAME, docModel.getName()); 1481 options.put(CoreEventConstants.DOCUMENT_DIRTY, dirty); 1482 notifyEvent(DocumentEventTypes.BEFORE_DOC_UPDATE, docModel, options, null, null, true, true); 1483 String name = (String) options.get(CoreEventConstants.DESTINATION_NAME); 1484 // did the event change the name? not applicable to Root whose 1485 // name is null/empty 1486 if (name != null && !name.equals(docModel.getName())) { 1487 doc = getSession().move(doc, doc.getParent(), name); 1488 } 1489 1490 // recompute the dirty state 1491 dirty = docModel.isDirty(); 1492 options.put(CoreEventConstants.DOCUMENT_DIRTY, dirty); 1493 1494 // recompute versioning option as it can be set by listeners 1495 VersioningOption versioningOption = (VersioningOption) docModel.getContextData(VersioningService.VERSIONING_OPTION); 1496 docModel.putContextData(VersioningService.VERSIONING_OPTION, null); 1497 String checkinComment = (String) docModel.getContextData(VersioningService.CHECKIN_COMMENT); 1498 docModel.putContextData(VersioningService.CHECKIN_COMMENT, null); 1499 Boolean disableAutoCheckOut = (Boolean) docModel.getContextData(VersioningService.DISABLE_AUTO_CHECKOUT); 1500 docModel.putContextData(VersioningService.DISABLE_AUTO_CHECKOUT, null); 1501 options.put(VersioningService.DISABLE_AUTO_CHECKOUT, disableAutoCheckOut); 1502 1503 boolean manualVersioning = versioningOption != null; 1504 if (!docModel.isImmutable()) { 1505 // compute auto versioning before update - here we create a version of document in order to save previous 1506 // state it's useful if we want to implement rules like create a version if last contributor is not the 1507 // same previous one. So we want to trigger this mechanism if and only if: 1508 // - previous document is checkouted 1509 // - we don't ask for a version without updating the document (manual versioning only) 1510 // no need to fire event, as we use DocumentModel API it's already done 1511 if (previousDocModel.isCheckedOut() && (!manualVersioning || dirty)) { 1512 getVersioningService().doAutomaticVersioning(previousDocModel, docModel, true); 1513 } 1514 // pre-save versioning 1515 boolean checkout = getVersioningService().isPreSaveDoingCheckOut(doc, dirty, versioningOption, options); 1516 if (checkout) { 1517 notifyEvent(DocumentEventTypes.ABOUT_TO_CHECKOUT, docModel, options, null, null, true, true); 1518 } 1519 versioningOption = getVersioningService().doPreSave(doc, dirty, versioningOption, checkinComment, options); 1520 if (checkout) { 1521 DocumentModel checkedOutDoc = readModel(doc); 1522 notifyEvent(DocumentEventTypes.DOCUMENT_CHECKEDOUT, checkedOutDoc, options, null, null, true, false); 1523 } 1524 } 1525 1526 boolean allowVersionWrite = Boolean.TRUE.equals(docModel.getContextData(ALLOW_VERSION_WRITE)); 1527 docModel.putContextData(ALLOW_VERSION_WRITE, null); 1528 boolean setReadWrite = allowVersionWrite && doc.isVersion() && doc.isReadOnly(); 1529 1530 // actual save 1531 if (setReadWrite) { 1532 doc.setReadOnly(false); 1533 } 1534 docModel = writeModel(doc, docModel); 1535 if (setReadWrite) { 1536 doc.setReadOnly(true); 1537 } 1538 1539 Document checkedInDoc = null; 1540 if (!docModel.isImmutable()) { 1541 // post-save versioning 1542 boolean checkin = getVersioningService().isPostSaveDoingCheckIn(doc, versioningOption, options); 1543 if (checkin) { 1544 notifyEvent(DocumentEventTypes.ABOUT_TO_CHECKIN, docModel, options, null, null, true, true); 1545 } 1546 if (manualVersioning) { 1547 checkedInDoc = getVersioningService().doPostSave(doc, versioningOption, checkinComment, options); 1548 } else { 1549 // compute auto versioning - only if it is not deactivated by manual versioning 1550 // no need to fire event, as we use DocumentModel API it's already done 1551 getVersioningService().doAutomaticVersioning(previousDocModel, docModel, false); 1552 } 1553 } 1554 1555 // post-save events 1556 docModel = readModel(doc); 1557 if (checkedInDoc != null) { 1558 DocumentRef checkedInVersionRef = new IdRef(checkedInDoc.getUUID()); 1559 notifyCheckedInVersion(docModel, checkedInVersionRef, options, checkinComment); 1560 } 1561 1562 notifyEvent(DocumentEventTypes.DOCUMENT_UPDATED, docModel, options, null, null, true, false); 1563 updateDocumentCount.inc(); 1564 return docModel; 1565 } 1566 1567 @Override 1568 public void saveDocuments(DocumentModel[] docModels) { 1569 // TODO: optimize this - avoid calling at each iteration saveDoc... 1570 for (DocumentModel docModel : docModels) { 1571 saveDocument(docModel); 1572 } 1573 } 1574 1575 @Override 1576 public DocumentModel getSourceDocument(DocumentRef docRef) { 1577 assert null != docRef; 1578 1579 Document doc = resolveReference(docRef); 1580 checkPermission(doc, READ_VERSION); 1581 Document headDocument = doc.getSourceDocument(); 1582 if (headDocument == null) { 1583 throw new DocumentNotFoundException("Source document has been deleted"); 1584 } 1585 return readModel(headDocument); 1586 } 1587 1588 protected VersionModel getVersionModel(Document version) { 1589 VersionModel versionModel = new VersionModelImpl(); 1590 versionModel.setId(version.getUUID()); 1591 versionModel.setCreated(version.getVersionCreationDate()); 1592 versionModel.setDescription(version.getCheckinComment()); 1593 versionModel.setLabel(version.getVersionLabel()); 1594 return versionModel; 1595 } 1596 1597 @Override 1598 public DocumentModel getLastDocumentVersion(DocumentRef docRef) { 1599 Document doc = resolveReference(docRef); 1600 checkPermission(doc, READ_VERSION); 1601 Document version = doc.getLastVersion(); 1602 return version == null ? null : readModel(version); 1603 } 1604 1605 @Override 1606 public DocumentRef getLastDocumentVersionRef(DocumentRef docRef) { 1607 Document doc = resolveReference(docRef); 1608 checkPermission(doc, READ_VERSION); 1609 Document version = doc.getLastVersion(); 1610 return version == null ? null : new IdRef(version.getUUID()); 1611 } 1612 1613 @Override 1614 public List<DocumentRef> getVersionsRefs(DocumentRef docRef) { 1615 Document doc = resolveReference(docRef); 1616 checkPermission(doc, READ_VERSION); 1617 List<String> ids = doc.getVersionsIds(); 1618 List<DocumentRef> refs = new ArrayList<>(ids.size()); 1619 for (String id : ids) { 1620 refs.add(new IdRef(id)); 1621 } 1622 return refs; 1623 } 1624 1625 @Override 1626 public List<DocumentModel> getVersions(DocumentRef docRef) { 1627 Document doc = resolveReference(docRef); 1628 checkPermission(doc, READ_VERSION); 1629 List<Document> docVersions = doc.getVersions(); 1630 List<DocumentModel> versions = new ArrayList<>(docVersions.size()); 1631 for (Document version : docVersions) { 1632 versions.add(readModel(version)); 1633 } 1634 return versions; 1635 } 1636 1637 @Override 1638 public List<VersionModel> getVersionsForDocument(DocumentRef docRef) { 1639 Document doc = resolveReference(docRef); 1640 checkPermission(doc, READ_VERSION); 1641 List<Document> docVersions = doc.getVersions(); 1642 List<VersionModel> versions = new ArrayList<>(docVersions.size()); 1643 for (Document version : docVersions) { 1644 versions.add(getVersionModel(version)); 1645 } 1646 return versions; 1647 1648 } 1649 1650 @Override 1651 public DocumentModel restoreToVersion(DocumentRef docRef, DocumentRef versionRef) { 1652 Document doc = resolveReference(docRef); 1653 Document ver = resolveReference(versionRef); 1654 return restoreToVersion(doc, ver, false, true); 1655 } 1656 1657 @Override 1658 public DocumentModel restoreToVersion(DocumentRef docRef, DocumentRef versionRef, boolean skipSnapshotCreation, 1659 boolean skipCheckout) { 1660 Document doc = resolveReference(docRef); 1661 Document ver = resolveReference(versionRef); 1662 return restoreToVersion(doc, ver, skipSnapshotCreation, skipCheckout); 1663 } 1664 1665 protected DocumentModel restoreToVersion(Document doc, Document version, boolean skipSnapshotCreation, 1666 boolean skipCheckout) { 1667 checkPermission(doc, WRITE_VERSION); 1668 1669 DocumentModel docModel = readModel(doc); 1670 1671 Map<String, Serializable> options = new HashMap<>(); 1672 1673 // we're about to overwrite the document, make sure it's archived 1674 if (!skipSnapshotCreation && doc.isCheckedOut()) { 1675 String checkinComment = (String) docModel.getContextData(VersioningService.CHECKIN_COMMENT); 1676 docModel.putContextData(VersioningService.CHECKIN_COMMENT, null); 1677 notifyEvent(DocumentEventTypes.ABOUT_TO_CHECKIN, docModel, options, null, null, true, true); 1678 Document ver = getVersioningService().doCheckIn(doc, null, checkinComment); 1679 docModel.refresh(DocumentModel.REFRESH_STATE, null); 1680 notifyCheckedInVersion(docModel, new IdRef(ver.getUUID()), null, checkinComment); 1681 } 1682 1683 // FIXME: the fields are hardcoded. should be moved in versioning 1684 // component 1685 // HOW? 1686 final Long majorVer = (Long) doc.getPropertyValue("major_version"); 1687 final Long minorVer = (Long) doc.getPropertyValue("minor_version"); 1688 if (majorVer != null || minorVer != null) { 1689 options.put(VersioningDocument.CURRENT_DOCUMENT_MAJOR_VERSION_KEY, majorVer); 1690 options.put(VersioningDocument.CURRENT_DOCUMENT_MINOR_VERSION_KEY, minorVer); 1691 } 1692 // add the uuid of the version being restored 1693 String versionUUID = version.getUUID(); 1694 options.put(VersioningDocument.RESTORED_VERSION_UUID_KEY, versionUUID); 1695 1696 notifyEvent(DocumentEventTypes.BEFORE_DOC_RESTORE, docModel, options, null, null, true, true); 1697 writeModel(doc, docModel); 1698 1699 doc.restore(version); 1700 // re-read doc model after restoration 1701 docModel = readModel(doc); 1702 notifyEvent(DocumentEventTypes.DOCUMENT_RESTORED, docModel, options, null, docModel.getVersionLabel(), true, 1703 false); 1704 docModel = writeModel(doc, docModel); 1705 1706 if (!skipCheckout) { 1707 // restore gives us a checked in document, so do a checkout 1708 notifyEvent(DocumentEventTypes.ABOUT_TO_CHECKOUT, docModel, options, null, null, true, true); 1709 getVersioningService().doCheckOut(doc); 1710 docModel = readModel(doc); 1711 notifyEvent(DocumentEventTypes.DOCUMENT_CHECKEDOUT, docModel, options, null, null, true, false); 1712 } 1713 1714 log.debug("Document restored to version:" + version.getUUID()); 1715 return docModel; 1716 } 1717 1718 @Override 1719 public DocumentRef getBaseVersion(DocumentRef docRef) { 1720 Document doc = resolveReference(docRef); 1721 checkPermission(doc, READ); 1722 Document ver = doc.getBaseVersion(); 1723 if (ver == null) { 1724 return null; 1725 } 1726 checkPermission(ver, READ); 1727 return new IdRef(ver.getUUID()); 1728 } 1729 1730 @Override 1731 public DocumentRef checkIn(DocumentRef docRef, VersioningOption option, String checkinComment) { 1732 Document doc = resolveReference(docRef); 1733 checkPermission(doc, WRITE_PROPERTIES); 1734 DocumentModel docModel = readModel(doc); 1735 1736 Map<String, Serializable> options = new HashMap<>(); 1737 notifyEvent(DocumentEventTypes.ABOUT_TO_CHECKIN, docModel, options, null, null, true, true); 1738 writeModel(doc, docModel); 1739 1740 Document version = getVersioningService().doCheckIn(doc, option, checkinComment); 1741 1742 docModel = readModel(doc); 1743 DocumentRef checkedInVersionRef = new IdRef(version.getUUID()); 1744 notifyCheckedInVersion(docModel, checkedInVersionRef, options, checkinComment); 1745 writeModel(doc, docModel); 1746 1747 return checkedInVersionRef; 1748 } 1749 1750 /** 1751 * Send a core event for the creation of a new check in version. The source document is the live document model used 1752 * as the source for the checkin, not the archived version it-self. 1753 * 1754 * @param docModel work document that has been checked-in as a version 1755 * @param checkedInVersionRef document ref of the new checked-in version 1756 * @param options initial option map, or null 1757 */ 1758 protected void notifyCheckedInVersion(DocumentModel docModel, DocumentRef checkedInVersionRef, 1759 Map<String, Serializable> options, String checkinComment) { 1760 String label = getVersioningService().getVersionLabel(docModel); 1761 Map<String, Serializable> props = new HashMap<>(); 1762 if (options != null) { 1763 props.putAll(options); 1764 } 1765 props.put("versionLabel", label); 1766 props.put("checkInComment", checkinComment); 1767 props.put("checkedInVersionRef", checkedInVersionRef); 1768 if (checkinComment == null && options != null) { 1769 // check if there's a comment already in options 1770 Object optionsComment = options.get("comment"); 1771 if (optionsComment instanceof String) { 1772 checkinComment = (String) optionsComment; 1773 } 1774 } 1775 String comment = checkinComment == null ? label : label + ' ' + checkinComment; 1776 props.put("comment", comment); // compat, used in audit 1777 // notify checkin on live document 1778 notifyEvent(DocumentEventTypes.DOCUMENT_CHECKEDIN, docModel, props, null, null, true, false); 1779 // notify creation on version document 1780 notifyEvent(DocumentEventTypes.DOCUMENT_CREATED, getDocument(checkedInVersionRef), props, null, null, true, 1781 false); 1782 1783 } 1784 1785 @Override 1786 public void checkOut(DocumentRef docRef) { 1787 Document doc = resolveReference(docRef); 1788 // TODO: add a new permission names CHECKOUT and use it instead of 1789 // WRITE_PROPERTIES 1790 checkPermission(doc, WRITE_PROPERTIES); 1791 DocumentModel docModel = readModel(doc); 1792 Map<String, Serializable> options = new HashMap<>(); 1793 1794 notifyEvent(DocumentEventTypes.ABOUT_TO_CHECKOUT, docModel, options, null, null, true, true); 1795 1796 getVersioningService().doCheckOut(doc); 1797 docModel = readModel(doc); 1798 1799 notifyEvent(DocumentEventTypes.DOCUMENT_CHECKEDOUT, docModel, options, null, null, true, false); 1800 writeModel(doc, docModel); 1801 } 1802 1803 @Override 1804 public boolean isCheckedOut(DocumentRef docRef) { 1805 assert null != docRef; 1806 Document doc = resolveReference(docRef); 1807 checkPermission(doc, BROWSE); 1808 return doc.isCheckedOut(); 1809 } 1810 1811 @Override 1812 public String getVersionSeriesId(DocumentRef docRef) { 1813 Document doc = resolveReference(docRef); 1814 checkPermission(doc, READ); 1815 return doc.getVersionSeriesId(); 1816 } 1817 1818 @Override 1819 public DocumentModel getWorkingCopy(DocumentRef docRef) { 1820 Document doc = resolveReference(docRef); 1821 checkPermission(doc, READ_VERSION); 1822 Document pwc = doc.getWorkingCopy(); 1823 checkPermission(pwc, READ); 1824 return pwc == null ? null : readModel(pwc); 1825 } 1826 1827 @Override 1828 public DocumentModel getVersion(String versionableId, VersionModel versionModel) { 1829 String id = versionModel.getId(); 1830 if (id != null) { 1831 return getDocument(new IdRef(id)); 1832 } 1833 Document doc = getSession().getVersion(versionableId, versionModel); 1834 if (doc == null) { 1835 return null; 1836 } 1837 checkPermission(doc, READ_PROPERTIES); 1838 checkPermission(doc, READ_VERSION); 1839 return readModel(doc); 1840 } 1841 1842 @Override 1843 public String getVersionLabel(DocumentModel docModel) { 1844 return getVersioningService().getVersionLabel(docModel); 1845 } 1846 1847 @Override 1848 public DocumentModel getDocumentWithVersion(DocumentRef docRef, VersionModel version) { 1849 String id = version.getId(); 1850 if (id != null) { 1851 return getDocument(new IdRef(id)); 1852 } 1853 Document doc = resolveReference(docRef); 1854 checkPermission(doc, READ_PROPERTIES); 1855 checkPermission(doc, READ_VERSION); 1856 String docPath = doc.getPath(); 1857 doc = doc.getVersion(version.getLabel()); 1858 if (doc == null) { 1859 // SQL Storage uses to return null if version not found 1860 log.debug("Version " + version.getLabel() + " does not exist for " + docPath); 1861 return null; 1862 } 1863 log.debug("Retrieved the version " + version.getLabel() + " of the document " + docPath); 1864 return readModel(doc); 1865 } 1866 1867 @Override 1868 public DocumentModel createProxy(DocumentRef docRef, DocumentRef folderRef) { 1869 Document doc = resolveReference(docRef); 1870 Document fold = resolveReference(folderRef); 1871 checkPermission(doc, READ); 1872 checkPermission(fold, ADD_CHILDREN); 1873 return createProxyInternal(doc, fold, new HashMap<>()); 1874 } 1875 1876 protected DocumentModel createProxyInternal(Document doc, Document folder, Map<String, Serializable> options) { 1877 // create the new proxy 1878 Document proxy = getSession().createProxy(doc, folder); 1879 DocumentModel proxyModel = readModel(proxy); 1880 1881 notifyEvent(DocumentEventTypes.DOCUMENT_CREATED, proxyModel, options, null, null, true, false); 1882 notifyEvent(DocumentEventTypes.DOCUMENT_PROXY_PUBLISHED, proxyModel, options, null, null, true, false); 1883 DocumentModel folderModel = readModel(folder); 1884 notifyEvent(DocumentEventTypes.SECTION_CONTENT_PUBLISHED, folderModel, options, null, null, true, false); 1885 return proxyModel; 1886 } 1887 1888 /** 1889 * Remove proxies for the same base document in the folder. doc may be a normal document or a proxy. 1890 */ 1891 protected List<String> removeExistingProxies(Document doc, Document folder) { 1892 Collection<Document> otherProxies = getSession().getProxies(doc, folder); 1893 List<String> removedProxyIds = new ArrayList<>(otherProxies.size()); 1894 for (Document otherProxy : otherProxies) { 1895 removedProxyIds.add(otherProxy.getUUID()); 1896 removeNotifyOneDoc(otherProxy); 1897 } 1898 return removedProxyIds; 1899 } 1900 1901 /** 1902 * Update the proxy for doc in the given section to point to the new target. Do nothing if there are several 1903 * proxies. 1904 * 1905 * @return the proxy if it was updated, or {@code null} if none or several were found 1906 */ 1907 protected DocumentModel updateExistingProxies(Document doc, Document folder, Document target) { 1908 Collection<Document> proxies = getSession().getProxies(doc, folder); 1909 try { 1910 if (proxies.size() == 1) { 1911 for (Document proxy : proxies) { 1912 proxy.setTargetDocument(target); 1913 return readModel(proxy); 1914 } 1915 } 1916 } catch (UnsupportedOperationException e) { 1917 log.error("Cannot update proxy, try to remove"); 1918 } 1919 return null; 1920 } 1921 1922 @Override 1923 public DocumentModelList getProxies(DocumentRef docRef, DocumentRef folderRef) { 1924 Document folder = null; 1925 if (folderRef != null) { 1926 folder = resolveReference(folderRef); 1927 checkPermission(folder, READ_CHILDREN); 1928 } 1929 Document doc = resolveReference(docRef); 1930 Collection<Document> children = getSession().getProxies(doc, folder); 1931 DocumentModelList docs = new DocumentModelListImpl(); 1932 for (Document child : children) { 1933 if (hasPermission(child, READ)) { 1934 docs.add(readModel(child)); 1935 } 1936 } 1937 return docs; 1938 } 1939 1940 @Override 1941 public List<String> getAvailableSecurityPermissions() { 1942 // XXX: add security check? 1943 return Arrays.asList(getSecurityService().getPermissionProvider().getPermissions()); 1944 } 1945 1946 @Override 1947 public DataModel getDataModel(DocumentRef docRef, Schema schema) { 1948 Document doc = resolveReference(docRef); 1949 checkPermission(doc, READ); 1950 return DocumentModelFactory.createDataModel(doc, schema); 1951 } 1952 1953 protected Object getDataModelField(DocumentRef docRef, String schema, String field) { 1954 Document doc = resolveReference(docRef); 1955 if (doc != null) { 1956 checkPermission(doc, READ); 1957 Schema docSchema = doc.getType().getSchema(schema); 1958 if (docSchema != null) { 1959 String prefix = docSchema.getNamespace().prefix; 1960 if (prefix != null && prefix.length() > 0) { 1961 field = prefix + ':' + field; 1962 } 1963 return doc.getPropertyValue(field); 1964 } else { 1965 log.warn("Cannot find schema with name=" + schema); 1966 } 1967 } else { 1968 log.warn("Cannot resolve docRef=" + docRef); 1969 } 1970 return null; 1971 } 1972 1973 @Override 1974 public String getCurrentLifeCycleState(DocumentRef docRef) { 1975 Document doc = resolveReference(docRef); 1976 checkPermission(doc, READ_LIFE_CYCLE); 1977 return doc.getLifeCycleState(); 1978 } 1979 1980 @Override 1981 public String getLifeCyclePolicy(DocumentRef docRef) { 1982 Document doc = resolveReference(docRef); 1983 checkPermission(doc, READ_LIFE_CYCLE); 1984 return doc.getLifeCyclePolicy(); 1985 } 1986 1987 /** 1988 * Make a document follow a transition. 1989 * 1990 * @param docRef a {@link DocumentRef} 1991 * @param transition the transition to follow 1992 * @param options an option map than can be used by callers to pass additional params 1993 * @since 5.9.3 1994 */ 1995 private boolean followTransition(DocumentRef docRef, String transition, Map<String, Serializable> options) 1996 throws LifeCycleException { 1997 Document doc = resolveReference(docRef); 1998 checkPermission(doc, WRITE_LIFE_CYCLE); 1999 2000 if (!doc.isVersion() && !doc.isProxy() && !doc.isCheckedOut()) { 2001 boolean deleteOrUndelete = LifeCycleConstants.DELETE_TRANSITION.equals(transition) 2002 || LifeCycleConstants.UNDELETE_TRANSITION.equals(transition); 2003 if (!deleteOrUndelete || Framework.getService(ConfigurationService.class).isBooleanPropertyFalse( 2004 TRASH_KEEP_CHECKED_IN_PROPERTY)) { 2005 checkOut(docRef); 2006 doc = resolveReference(docRef); 2007 } 2008 } 2009 String formerStateName = doc.getLifeCycleState(); 2010 doc.followTransition(transition); 2011 2012 // Construct a map holding meta information about the event. 2013 Map<String, Serializable> eventOptions = new HashMap<>(); 2014 eventOptions.put(org.nuxeo.ecm.core.api.LifeCycleConstants.TRANSTION_EVENT_OPTION_FROM, formerStateName); 2015 eventOptions.put(org.nuxeo.ecm.core.api.LifeCycleConstants.TRANSTION_EVENT_OPTION_TO, doc.getLifeCycleState()); 2016 eventOptions.put(org.nuxeo.ecm.core.api.LifeCycleConstants.TRANSTION_EVENT_OPTION_TRANSITION, transition); 2017 String comment = (String) options.get("comment"); 2018 DocumentModel docModel = readModel(doc); 2019 notifyEvent(org.nuxeo.ecm.core.api.LifeCycleConstants.TRANSITION_EVENT, docModel, eventOptions, 2020 DocumentEventCategories.EVENT_LIFE_CYCLE_CATEGORY, comment, true, false); 2021 if (!docModel.isImmutable()) { 2022 writeModel(doc, docModel); 2023 } 2024 return true; // throws if error 2025 } 2026 2027 @Override 2028 public boolean followTransition(DocumentModel docModel, String transition) throws LifeCycleException { 2029 return followTransition(docModel.getRef(), transition, docModel.getContextData()); 2030 } 2031 2032 @Override 2033 public boolean followTransition(DocumentRef docRef, String transition) throws LifeCycleException { 2034 return followTransition(docRef, transition, Collections.emptyMap()); 2035 } 2036 2037 @Override 2038 public Collection<String> getAllowedStateTransitions(DocumentRef docRef) { 2039 Document doc = resolveReference(docRef); 2040 checkPermission(doc, READ_LIFE_CYCLE); 2041 return doc.getAllowedStateTransitions(); 2042 } 2043 2044 @Override 2045 public void reinitLifeCycleState(DocumentRef docRef) { 2046 Document doc = resolveReference(docRef); 2047 checkPermission(doc, WRITE_LIFE_CYCLE); 2048 getLifeCycleService().reinitLifeCycle(doc); 2049 } 2050 2051 @Override 2052 public Object[] getDataModelsField(DocumentRef[] docRefs, String schema, String field) { 2053 2054 assert docRefs != null; 2055 assert schema != null; 2056 assert field != null; 2057 2058 final Object[] values = new Object[docRefs.length]; 2059 int i = 0; 2060 for (DocumentRef docRef : docRefs) { 2061 final Object value = getDataModelField(docRef, schema, field); 2062 values[i++] = value; 2063 } 2064 2065 return values; 2066 } 2067 2068 @Override 2069 public DocumentRef[] getParentDocumentRefs(DocumentRef docRef) { 2070 final List<DocumentRef> docRefs = new ArrayList<>(); 2071 final Document doc = resolveReference(docRef); 2072 Document parentDoc = doc.getParent(); 2073 while (parentDoc != null) { 2074 final DocumentRef parentDocRef = new IdRef(parentDoc.getUUID()); 2075 docRefs.add(parentDocRef); 2076 parentDoc = parentDoc.getParent(); 2077 } 2078 DocumentRef[] refs = new DocumentRef[docRefs.size()]; 2079 return docRefs.toArray(refs); 2080 } 2081 2082 @Override 2083 public Object[] getDataModelsFieldUp(DocumentRef docRef, String schema, String field) { 2084 2085 final DocumentRef[] parentRefs = getParentDocumentRefs(docRef); 2086 final DocumentRef[] allRefs = new DocumentRef[parentRefs.length + 1]; 2087 allRefs[0] = docRef; 2088 System.arraycopy(parentRefs, 0, allRefs, 1, parentRefs.length); 2089 2090 return getDataModelsField(allRefs, schema, field); 2091 } 2092 2093 @Override 2094 public Lock setLock(DocumentRef docRef) throws LockException { 2095 Document doc = resolveReference(docRef); 2096 // TODO: add a new permission named LOCK and use it instead of 2097 // WRITE_PROPERTIES 2098 checkPermission(doc, WRITE_PROPERTIES); 2099 Lock lock = new Lock(getPrincipal().getName(), new GregorianCalendar()); 2100 Lock oldLock = doc.setLock(lock); 2101 if (oldLock != null) { 2102 throw new LockException("Document already locked by " + oldLock.getOwner() + ": " + docRef); 2103 } 2104 DocumentModel docModel = readModel(doc); 2105 Map<String, Serializable> options = new HashMap<>(); 2106 options.put("lock", lock); 2107 notifyEvent(DocumentEventTypes.DOCUMENT_LOCKED, docModel, options, null, null, true, false); 2108 return lock; 2109 } 2110 2111 @Override 2112 public Lock getLockInfo(DocumentRef docRef) { 2113 Document doc = resolveReference(docRef); 2114 checkPermission(doc, READ); 2115 return doc.getLock(); 2116 } 2117 2118 @Override 2119 public Lock removeLock(DocumentRef docRef) throws LockException { 2120 Document doc = resolveReference(docRef); 2121 String owner; 2122 if (hasPermission(docRef, UNLOCK)) { 2123 // always unlock 2124 owner = null; 2125 } else { 2126 owner = getPrincipal().getName(); 2127 } 2128 Lock lock = doc.removeLock(owner); 2129 if (lock == null) { 2130 // there was no lock, we're done 2131 return null; 2132 } 2133 if (lock.getFailed()) { 2134 // lock removal failed due to owner check 2135 throw new LockException("Document already locked by " + lock.getOwner() + ": " + docRef); 2136 } 2137 DocumentModel docModel = readModel(doc); 2138 Map<String, Serializable> options = new HashMap<>(); 2139 options.put("lock", lock); 2140 notifyEvent(DocumentEventTypes.DOCUMENT_UNLOCKED, docModel, options, null, null, true, false); 2141 return lock; 2142 } 2143 2144 protected boolean isAdministrator() { 2145 Principal principal = getPrincipal(); 2146 // FIXME: this is inconsistent with NuxeoPrincipal#isAdministrator 2147 // method because it allows hardcoded Administrator user 2148 if (Framework.isTestModeSet()) { 2149 if (SecurityConstants.ADMINISTRATOR.equals(principal.getName())) { 2150 return true; 2151 } 2152 } 2153 if (SYSTEM_USERNAME.equals(principal.getName())) { 2154 return true; 2155 } 2156 if (principal instanceof NuxeoPrincipal) { 2157 return ((NuxeoPrincipal) principal).isAdministrator(); 2158 } 2159 return false; 2160 } 2161 2162 @Override 2163 public void applyDefaultPermissions(String userOrGroupName) { 2164 if (userOrGroupName == null) { 2165 throw new NullPointerException("null userOrGroupName"); 2166 } 2167 if (!isAdministrator()) { 2168 throw new DocumentSecurityException("You need to be an Administrator to do this."); 2169 } 2170 DocumentModel rootDocument = getRootDocument(); 2171 ACP acp = new ACPImpl(); 2172 2173 UserEntry userEntry = new UserEntryImpl(userOrGroupName); 2174 userEntry.addPrivilege(READ); 2175 2176 acp.setRules(new UserEntry[] { userEntry }); 2177 2178 setACP(rootDocument.getRef(), acp, false); 2179 } 2180 2181 @Override 2182 public DocumentModel publishDocument(DocumentModel docToPublish, DocumentModel section) { 2183 return publishDocument(docToPublish, section, true); 2184 } 2185 2186 @Override 2187 public DocumentModel publishDocument(DocumentModel docModel, DocumentModel section, boolean overwriteExistingProxy) { 2188 Document doc = resolveReference(docModel.getRef()); 2189 Document sec = resolveReference(section.getRef()); 2190 checkPermission(doc, READ); 2191 checkPermission(sec, ADD_CHILDREN); 2192 2193 Map<String, Serializable> options = new HashMap<>(); 2194 DocumentModel proxy = null; 2195 Document target; 2196 if (docModel.isProxy() || docModel.isVersion()) { 2197 target = doc; 2198 if (overwriteExistingProxy) { 2199 if (docModel.isVersion()) { 2200 Document base = resolveReference(new IdRef(doc.getVersionSeriesId())); 2201 proxy = updateExistingProxies(base, sec, target); 2202 } 2203 if (proxy == null) { 2204 // remove previous 2205 List<String> removedProxyIds = removeExistingProxies(doc, sec); 2206 options.put(CoreEventConstants.REPLACED_PROXY_IDS, (Serializable) removedProxyIds); 2207 } 2208 } 2209 2210 } else { 2211 String checkinComment = (String) docModel.getContextData(VersioningService.CHECKIN_COMMENT); 2212 docModel.putContextData(VersioningService.CHECKIN_COMMENT, null); 2213 if (doc.isCheckedOut() || doc.getLastVersion() == null) { 2214 if (!doc.isCheckedOut()) { 2215 // last version was deleted while leaving a checked in 2216 // doc. recreate a version 2217 notifyEvent(DocumentEventTypes.ABOUT_TO_CHECKOUT, docModel, options, null, null, true, true); 2218 getVersioningService().doCheckOut(doc); 2219 docModel = readModel(doc); 2220 notifyEvent(DocumentEventTypes.DOCUMENT_CHECKEDOUT, docModel, options, null, null, true, false); 2221 } 2222 notifyEvent(DocumentEventTypes.ABOUT_TO_CHECKIN, docModel, options, null, null, true, true); 2223 Document version = getVersioningService().doCheckIn(doc, null, checkinComment); 2224 docModel.refresh(DocumentModel.REFRESH_STATE | DocumentModel.REFRESH_CONTENT_LAZY, null); 2225 notifyCheckedInVersion(docModel, new IdRef(version.getUUID()), null, checkinComment); 2226 } 2227 // NXP-12921: use base version because we could need to publish 2228 // a previous version (after restoring for example) 2229 target = doc.getBaseVersion(); 2230 if (overwriteExistingProxy) { 2231 proxy = updateExistingProxies(doc, sec, target); 2232 if (proxy == null) { 2233 // no or several proxies, remove them 2234 List<String> removedProxyIds = removeExistingProxies(doc, sec); 2235 options.put(CoreEventConstants.REPLACED_PROXY_IDS, (Serializable) removedProxyIds); 2236 } else { 2237 // notify proxy updates 2238 notifyEvent(DocumentEventTypes.DOCUMENT_PROXY_UPDATED, proxy, options, null, null, true, false); 2239 notifyEvent(DocumentEventTypes.DOCUMENT_PROXY_PUBLISHED, proxy, options, null, null, true, false); 2240 notifyEvent(DocumentEventTypes.SECTION_CONTENT_PUBLISHED, section, options, null, null, true, false); 2241 } 2242 } 2243 } 2244 if (proxy == null) { 2245 proxy = createProxyInternal(target, sec, options); 2246 } 2247 return proxy; 2248 } 2249 2250 @Override 2251 public String getSuperParentType(DocumentModel doc) { 2252 DocumentModel superSpace = getSuperSpace(doc); 2253 if (superSpace == null) { 2254 return null; 2255 } else { 2256 return superSpace.getType(); 2257 } 2258 } 2259 2260 @Override 2261 public DocumentModel getSuperSpace(DocumentModel doc) { 2262 if (doc == null) { 2263 throw new IllegalArgumentException("null document"); 2264 } 2265 if (doc.hasFacet(FacetNames.SUPER_SPACE)) { 2266 return doc; 2267 } else { 2268 2269 DocumentModel parent = getDirectAccessibleParent(doc.getRef()); 2270 if (parent == null || "/".equals(parent.getPathAsString())) { 2271 // return Root instead of null 2272 return getRootDocument(); 2273 } else { 2274 return getSuperSpace(parent); 2275 } 2276 } 2277 } 2278 2279 // walk the tree up until a accessible doc is found 2280 private DocumentModel getDirectAccessibleParent(DocumentRef docRef) { 2281 Document doc = resolveReference(docRef); 2282 Document parentDoc = doc.getParent(); 2283 if (parentDoc == null) { 2284 // return null for placeless document 2285 return null; 2286 } 2287 if (!hasPermission(parentDoc, READ)) { 2288 String parentPath = parentDoc.getPath(); 2289 if ("/".equals(parentPath)) { 2290 return getRootDocument(); 2291 } else { 2292 // try on parent 2293 return getDirectAccessibleParent(new PathRef(parentDoc.getPath())); 2294 } 2295 } 2296 return readModel(parentDoc); 2297 } 2298 2299 @Override 2300 public <T extends Serializable> T getDocumentSystemProp(DocumentRef ref, String systemProperty, Class<T> type) { 2301 Document doc = resolveReference(ref); 2302 return doc.getSystemProp(systemProperty, type); 2303 } 2304 2305 @Override 2306 public <T extends Serializable> void setDocumentSystemProp(DocumentRef ref, String systemProperty, T value) { 2307 Document doc = resolveReference(ref); 2308 if (systemProperty != null && systemProperty.startsWith(BINARY_TEXT_SYS_PROP)) { 2309 DocumentModel docModel = readModel(doc); 2310 Map<String, Serializable> options = new HashMap<>(); 2311 options.put(systemProperty, value != null); 2312 notifyEvent(DocumentEventTypes.BINARYTEXT_UPDATED, docModel, options, null, null, false, true); 2313 } 2314 doc.setSystemProp(systemProperty, value); 2315 } 2316 2317 @Override 2318 public String getChangeToken(DocumentRef ref) { 2319 Document doc = resolveReference(ref); 2320 return doc.getChangeToken(); 2321 } 2322 2323 @Override 2324 public void orderBefore(DocumentRef parent, String src, String dest) { 2325 if ((src == null) || (src.equals(dest))) { 2326 return; 2327 } 2328 Document doc = resolveReference(parent); 2329 doc.orderBefore(src, dest); 2330 Map<String, Serializable> options = new HashMap<>(); 2331 2332 // send event on container passing the reordered child as parameter 2333 DocumentModel docModel = readModel(doc); 2334 options.put(CoreEventConstants.REORDERED_CHILD, src); 2335 notifyEvent(DocumentEventTypes.DOCUMENT_CHILDREN_ORDER_CHANGED, docModel, options, null, src, true, false); 2336 } 2337 2338 @Override 2339 public DocumentModelRefresh refreshDocument(DocumentRef ref, int refreshFlags, String[] schemas) { 2340 Document doc = resolveReference(ref); 2341 2342 // permission checks 2343 if ((refreshFlags & (DocumentModel.REFRESH_PREFETCH | DocumentModel.REFRESH_STATE | DocumentModel.REFRESH_CONTENT)) != 0) { 2344 checkPermission(doc, READ); 2345 } 2346 if ((refreshFlags & DocumentModel.REFRESH_ACP) != 0) { 2347 checkPermission(doc, READ_SECURITY); 2348 } 2349 2350 DocumentModelRefresh refresh = DocumentModelFactory.refreshDocumentModel(doc, refreshFlags, schemas); 2351 2352 // ACPs need the session, so aren't done in the factory method 2353 if ((refreshFlags & DocumentModel.REFRESH_ACP) != 0) { 2354 refresh.acp = getSession().getMergedACP(doc); 2355 } 2356 2357 return refresh; 2358 } 2359 2360 @Override 2361 public String[] getPermissionsToCheck(String permission) { 2362 return getSecurityService().getPermissionsToCheck(permission); 2363 } 2364 2365 @Override 2366 public <T extends DetachedAdapter> T adaptFirstMatchingDocumentWithFacet(DocumentRef docRef, String facet, 2367 Class<T> adapterClass) { 2368 Document doc = getFirstParentDocumentWithFacet(docRef, facet); 2369 if (doc != null) { 2370 DocumentModel docModel = readModel(doc); 2371 loadDataModelsForFacet(docModel, doc, facet); 2372 docModel.detach(false); 2373 return docModel.getAdapter(adapterClass); 2374 } 2375 return null; 2376 } 2377 2378 protected void loadDataModelsForFacet(DocumentModel docModel, Document doc, String facetName) { 2379 // Load all the data related to facet's schemas 2380 SchemaManager schemaManager = Framework.getLocalService(SchemaManager.class); 2381 CompositeType facet = schemaManager.getFacet(facetName); 2382 if (facet == null) { 2383 return; 2384 } 2385 2386 String[] facetSchemas = facet.getSchemaNames(); 2387 for (String schema : facetSchemas) { 2388 DataModel dm = DocumentModelFactory.createDataModel(doc, schemaManager.getSchema(schema)); 2389 docModel.getDataModels().put(schema, dm); 2390 } 2391 } 2392 2393 /** 2394 * Returns the first {@code Document} with the given {@code facet}, recursively going up the parent hierarchy. 2395 * Returns {@code null} if there is no more parent. 2396 * <p> 2397 * This method does not check security rights. 2398 */ 2399 protected Document getFirstParentDocumentWithFacet(DocumentRef docRef, String facet) { 2400 Document doc = resolveReference(docRef); 2401 while (doc != null && !doc.hasFacet(facet)) { 2402 doc = doc.getParent(); 2403 } 2404 return doc; 2405 } 2406 2407 @Override 2408 public Map<String, String> getBinaryFulltext(DocumentRef ref) { 2409 Document doc = resolveReference(ref); 2410 checkPermission(doc, READ); 2411 // Use an id whether than system properties to avoid to store fulltext properties in cache 2412 String id = doc.getUUID(); 2413 if (doc.isProxy()) { 2414 id = doc.getTargetDocument().getUUID(); 2415 } 2416 return getSession().getBinaryFulltext(id); 2417 } 2418 2419}