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