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