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