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