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