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