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