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