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