001/* 002 * Copyright (c) 2006-2011 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 */ 013package org.nuxeo.ecm.core.api.impl; 014 015import static org.apache.commons.lang.ObjectUtils.NULL; 016import static org.nuxeo.ecm.core.schema.types.ComplexTypeImpl.canonicalXPath; 017 018import java.io.Serializable; 019import java.lang.reflect.Array; 020import java.text.DateFormat; 021import java.util.ArrayList; 022import java.util.Arrays; 023import java.util.Calendar; 024import java.util.Collection; 025import java.util.Collections; 026import java.util.Date; 027import java.util.HashMap; 028import java.util.HashSet; 029import java.util.List; 030import java.util.Map; 031import java.util.Set; 032 033import org.apache.commons.logging.Log; 034import org.apache.commons.logging.LogFactory; 035import org.nuxeo.common.collections.ArrayMap; 036import org.nuxeo.common.collections.PrimitiveArrays; 037import org.nuxeo.common.collections.ScopeType; 038import org.nuxeo.common.collections.ScopedMap; 039import org.nuxeo.common.utils.Path; 040import org.nuxeo.ecm.core.api.Blob; 041import org.nuxeo.ecm.core.api.CoreInstance; 042import org.nuxeo.ecm.core.api.CoreSession; 043import org.nuxeo.ecm.core.api.DataModel; 044import org.nuxeo.ecm.core.api.DataModelMap; 045import org.nuxeo.ecm.core.api.DocumentModel; 046import org.nuxeo.ecm.core.api.DocumentRef; 047import org.nuxeo.ecm.core.api.Lock; 048import org.nuxeo.ecm.core.api.NuxeoException; 049import org.nuxeo.ecm.core.api.NuxeoPrincipal; 050import org.nuxeo.ecm.core.api.PathRef; 051import org.nuxeo.ecm.core.api.PropertyException; 052import org.nuxeo.ecm.core.api.VersioningOption; 053import org.nuxeo.ecm.core.api.adapter.DocumentAdapterDescriptor; 054import org.nuxeo.ecm.core.api.adapter.DocumentAdapterService; 055import org.nuxeo.ecm.core.api.local.ClientLoginModule; 056import org.nuxeo.ecm.core.api.model.DocumentPart; 057import org.nuxeo.ecm.core.api.model.Property; 058import org.nuxeo.ecm.core.api.model.PropertyNotFoundException; 059import org.nuxeo.ecm.core.api.model.PropertyVisitor; 060import org.nuxeo.ecm.core.api.model.impl.DocumentPartImpl; 061import org.nuxeo.ecm.core.api.model.resolver.DocumentPropertyObjectResolverImpl; 062import org.nuxeo.ecm.core.api.model.resolver.PropertyObjectResolver; 063import org.nuxeo.ecm.core.api.security.ACP; 064import org.nuxeo.ecm.core.schema.DocumentType; 065import org.nuxeo.ecm.core.schema.FacetNames; 066import org.nuxeo.ecm.core.schema.Prefetch; 067import org.nuxeo.ecm.core.schema.SchemaManager; 068import org.nuxeo.ecm.core.schema.TypeConstants; 069import org.nuxeo.ecm.core.schema.TypeProvider; 070import org.nuxeo.ecm.core.schema.types.ComplexType; 071import org.nuxeo.ecm.core.schema.types.CompositeType; 072import org.nuxeo.ecm.core.schema.types.Field; 073import org.nuxeo.ecm.core.schema.types.JavaTypes; 074import org.nuxeo.ecm.core.schema.types.ListType; 075import org.nuxeo.ecm.core.schema.types.Schema; 076import org.nuxeo.ecm.core.schema.types.Type; 077import org.nuxeo.runtime.api.Framework; 078 079/** 080 * Standard implementation of a {@link DocumentModel}. 081 */ 082public class DocumentModelImpl implements DocumentModel, Cloneable { 083 084 private static final long serialVersionUID = 1L; 085 086 public static final String STRICT_LAZY_LOADING_POLICY_KEY = "org.nuxeo.ecm.core.strictlazyloading"; 087 088 public static final long F_VERSION = 16L; 089 090 public static final long F_PROXY = 32L; 091 092 public static final long F_IMMUTABLE = 256L; 093 094 private static final Log log = LogFactory.getLog(DocumentModelImpl.class); 095 096 protected String sid; 097 098 protected DocumentRef ref; 099 100 protected DocumentType type; 101 102 // for tests, keep the type name even if no actual type is registered 103 protected String typeName; 104 105 /** Schemas including those from instance facets. */ 106 protected Set<String> schemas; 107 108 /** Schemas including those from instance facets when the doc was read */ 109 protected Set<String> schemasOrig; 110 111 /** Facets including those on instance. */ 112 protected Set<String> facets; 113 114 /** Instance facets. */ 115 public Set<String> instanceFacets; 116 117 /** Instance facets when the document was read. */ 118 public Set<String> instanceFacetsOrig; 119 120 protected String id; 121 122 protected Path path; 123 124 protected Long pos; 125 126 protected DataModelMap dataModels; 127 128 protected DocumentRef parentRef; 129 130 protected static final Lock LOCK_UNKNOWN = new Lock(null, null); 131 132 protected Lock lock = LOCK_UNKNOWN; 133 134 /** state is lifecycle, version stuff. */ 135 protected boolean isStateLoaded; 136 137 // loaded if isStateLoaded 138 protected String currentLifeCycleState; 139 140 // loaded if isStateLoaded 141 protected String lifeCyclePolicy; 142 143 // loaded if isStateLoaded 144 protected boolean isCheckedOut = true; 145 146 // loaded if isStateLoaded 147 protected String versionSeriesId; 148 149 // loaded if isStateLoaded 150 protected boolean isLatestVersion; 151 152 // loaded if isStateLoaded 153 protected boolean isMajorVersion; 154 155 // loaded if isStateLoaded 156 protected boolean isLatestMajorVersion; 157 158 // loaded if isStateLoaded 159 protected boolean isVersionSeriesCheckedOut; 160 161 // loaded if isStateLoaded 162 protected String checkinComment; 163 164 // acp is not send between client/server 165 // it will be loaded lazy first time it is accessed 166 // and discarded when object is serialized 167 protected transient ACP acp; 168 169 // whether the acp was cached 170 protected transient boolean isACPLoaded = false; 171 172 // the adapters registered for this document - only valid on client 173 protected transient ArrayMap<Class<?>, Object> adapters; 174 175 /** 176 * Flags: bitwise combination of {@link #F_VERSION}, {@link #F_PROXY}, {@link #F_IMMUTABLE}. 177 */ 178 private long flags = 0L; 179 180 protected String repositoryName; 181 182 protected String sourceId; 183 184 private ScopedMap contextData; 185 186 // public for unit tests 187 public Prefetch prefetch; 188 189 private String detachedVersionLabel; 190 191 protected static Boolean strictSessionManagement; 192 193 protected DocumentModelImpl() { 194 } 195 196 /** 197 * Constructor to use a document model client side without referencing a document. 198 * <p> 199 * It must at least contain the type. 200 */ 201 public DocumentModelImpl(String typeName) { 202 SchemaManager schemaManager = Framework.getLocalService(SchemaManager.class); 203 if (schemaManager == null) { 204 throw new NullPointerException("No registered SchemaManager"); 205 } 206 type = schemaManager.getDocumentType(typeName); 207 this.typeName = typeName; 208 dataModels = new DataModelMapImpl(); 209 contextData = new ScopedMap(); 210 instanceFacets = new HashSet<String>(); 211 instanceFacetsOrig = new HashSet<String>(); 212 facets = new HashSet<String>(); 213 schemas = new HashSet<String>(); 214 schemasOrig = new HashSet<String>(); 215 } 216 217 /** 218 * Constructor to be used by clients. 219 * <p> 220 * A client constructed data model must contain at least the path and the type. 221 */ 222 public DocumentModelImpl(String parentPath, String name, String type) { 223 this(type); 224 String fullPath = parentPath == null ? name : parentPath + (parentPath.endsWith("/") ? "" : "/") + name; 225 path = new Path(fullPath); 226 ref = new PathRef(fullPath); 227 instanceFacets = new HashSet<String>(); 228 instanceFacetsOrig = new HashSet<String>(); 229 facets = new HashSet<String>(); 230 schemas = new HashSet<String>(); 231 if (getDocumentType() != null) { 232 facets.addAll(getDocumentType().getFacets()); 233 } 234 schemas = computeSchemas(getDocumentType(), instanceFacets, false); 235 schemasOrig = new HashSet<String>(schemas); 236 } 237 238 /** 239 * Constructor. 240 * <p> 241 * The lock parameter is unused since 5.4.2. 242 * 243 * @param facets the per-instance facets 244 */ 245 public DocumentModelImpl(String sid, String type, String id, Path path, Lock lock, DocumentRef docRef, 246 DocumentRef parentRef, String[] schemas, Set<String> facets, String sourceId, String repositoryName) { 247 this(sid, type, id, path, docRef, parentRef, schemas, facets, sourceId, repositoryName, false); 248 } 249 250 public DocumentModelImpl(String sid, String type, String id, Path path, DocumentRef docRef, DocumentRef parentRef, 251 String[] schemas, Set<String> facets, String sourceId, String repositoryName, boolean isProxy) { 252 this(type); 253 this.sid = sid; 254 this.id = id; 255 this.path = path; 256 ref = docRef; 257 this.parentRef = parentRef; 258 instanceFacets = facets == null ? new HashSet<String>() : new HashSet<String>(facets); 259 instanceFacetsOrig = new HashSet<String>(instanceFacets); 260 this.facets = new HashSet<String>(instanceFacets); 261 if (getDocumentType() != null) { 262 this.facets.addAll(getDocumentType().getFacets()); 263 } 264 if (schemas == null) { 265 this.schemas = computeSchemas(getDocumentType(), instanceFacets, isProxy); 266 } else { 267 this.schemas = new HashSet<String>(Arrays.asList(schemas)); 268 } 269 schemasOrig = new HashSet<String>(this.schemas); 270 this.repositoryName = repositoryName; 271 this.sourceId = sourceId; 272 setIsProxy(isProxy); 273 } 274 275 /** 276 * Recomputes effective schemas from a type + instance facets. 277 */ 278 public static Set<String> computeSchemas(DocumentType type, Collection<String> instanceFacets, boolean isProxy) { 279 Set<String> schemas = new HashSet<String>(); 280 if (type != null) { 281 schemas.addAll(Arrays.asList(type.getSchemaNames())); 282 } 283 TypeProvider typeProvider = Framework.getLocalService(SchemaManager.class); 284 for (String facet : instanceFacets) { 285 CompositeType facetType = typeProvider.getFacet(facet); 286 if (facetType != null) { // ignore pseudo-facets like Immutable 287 schemas.addAll(Arrays.asList(facetType.getSchemaNames())); 288 } 289 } 290 if (isProxy) { 291 for (Schema schema : typeProvider.getProxySchemas(type.getName())) { 292 schemas.add(schema.getName()); 293 } 294 } 295 return schemas; 296 } 297 298 public DocumentModelImpl(DocumentModel parent, String name, String type) { 299 this(parent.getPathAsString(), name, type); 300 } 301 302 @Override 303 public DocumentType getDocumentType() { 304 return type; 305 } 306 307 /** 308 * Gets the title from the dublincore schema. 309 * 310 * @see DocumentModel#getTitle() 311 */ 312 @Override 313 public String getTitle() { 314 String title = (String) getProperty("dublincore", "title"); 315 if (title != null) { 316 return title; 317 } 318 title = getName(); 319 if (title != null) { 320 return title; 321 } 322 return id; 323 } 324 325 @Override 326 public String getSessionId() { 327 return sid; 328 } 329 330 @Override 331 public DocumentRef getRef() { 332 return ref; 333 } 334 335 @Override 336 public DocumentRef getParentRef() { 337 if (parentRef == null && path != null) { 338 if (path.isAbsolute()) { 339 Path parentPath = path.removeLastSegments(1); 340 parentRef = new PathRef(parentPath.toString()); 341 } 342 // else keep parentRef null 343 } 344 return parentRef; 345 } 346 347 @Override 348 public CoreSession getCoreSession() { 349 if (sid == null) { 350 return null; 351 } 352 try { 353 return CoreInstance.getInstance().getSession(sid); 354 } catch (RuntimeException e) { 355 String messageTemp = "Try to get session closed %s. Document path %s, user connected %s"; 356 NuxeoPrincipal principal = ClientLoginModule.getCurrentPrincipal(); 357 String username = principal == null ? "null" : principal.getName(); 358 String message = String.format(messageTemp, sid, getPathAsString(), username); 359 log.error(message); 360 throw e; 361 } 362 } 363 364 protected boolean useStrictSessionManagement() { 365 if (strictSessionManagement == null) { 366 strictSessionManagement = Boolean.valueOf(Framework.isBooleanPropertyTrue(STRICT_LAZY_LOADING_POLICY_KEY)); 367 } 368 return strictSessionManagement.booleanValue(); 369 } 370 371 protected CoreSession getTempCoreSession() { 372 if (sid != null) { 373 // detached docs need a tmp session anyway 374 if (useStrictSessionManagement()) { 375 throw new NuxeoException("Document " + id + " is bound to a closed CoreSession, can not reconnect"); 376 } 377 } 378 return CoreInstance.openCoreSession(repositoryName); 379 } 380 381 protected abstract class RunWithCoreSession<T> { 382 public CoreSession session; 383 384 public abstract T run(); 385 386 public T execute() { 387 session = getCoreSession(); 388 if (session != null) { 389 return run(); 390 } else { 391 session = getTempCoreSession(); 392 try { 393 return run(); 394 } finally { 395 try { 396 session.save(); 397 } finally { 398 session.close(); 399 } 400 } 401 } 402 } 403 } 404 405 @Override 406 public void detach(boolean loadAll) { 407 if (sid == null) { 408 return; 409 } 410 if (loadAll) { 411 for (String schema : schemas) { 412 if (!isSchemaLoaded(schema)) { 413 loadDataModel(schema); 414 } 415 } 416 // fetch ACP too if possible 417 if (ref != null) { 418 getACP(); 419 } 420 detachedVersionLabel = getVersionLabel(); 421 // load some system info 422 isCheckedOut(); 423 getCurrentLifeCycleState(); 424 getLockInfo(); 425 } 426 sid = null; 427 } 428 429 @Override 430 public void attach(String sid) { 431 if (this.sid != null) { 432 throw new NuxeoException("Cannot attach a document that is already attached"); 433 } 434 this.sid = sid; 435 } 436 437 /** 438 * Lazily loads the given data model. 439 */ 440 protected final DataModel loadDataModel(String schema) { 441 442 if (log.isTraceEnabled()) { 443 log.trace("lazy loading of schema " + schema + " for doc " + toString()); 444 } 445 446 if (!schemas.contains(schema)) { 447 return null; 448 } 449 if (!schemasOrig.contains(schema)) { 450 // not present yet in persistent document 451 DataModel dataModel = new DataModelImpl(schema); 452 dataModels.put(schema, dataModel); 453 return dataModel; 454 } 455 if (sid == null) { 456 // supports non bound docs 457 DataModel dataModel = new DataModelImpl(schema); 458 dataModels.put(schema, dataModel); 459 return dataModel; 460 } 461 if (ref == null) { 462 return null; 463 } 464 // load from session 465 if (getCoreSession() == null && useStrictSessionManagement()) { 466 log.warn("DocumentModel " + id + " is bound to a null or closed session, " 467 + "lazy loading is not available"); 468 return null; 469 } 470 TypeProvider typeProvider = Framework.getLocalService(SchemaManager.class); 471 final Schema schemaType = typeProvider.getSchema(schema); 472 DataModel dataModel = new RunWithCoreSession<DataModel>() { 473 @Override 474 public DataModel run() { 475 return session.getDataModel(ref, schemaType); 476 } 477 }.execute(); 478 dataModels.put(schema, dataModel); 479 return dataModel; 480 } 481 482 @Override 483 public DataModel getDataModel(String schema) { 484 DataModel dataModel = dataModels.get(schema); 485 if (dataModel == null) { 486 dataModel = loadDataModel(schema); 487 } 488 return dataModel; 489 } 490 491 @Override 492 public Collection<DataModel> getDataModelsCollection() { 493 return dataModels.values(); 494 } 495 496 public void addDataModel(DataModel dataModel) { 497 dataModels.put(dataModel.getSchema(), dataModel); 498 } 499 500 @Override 501 public String[] getSchemas() { 502 return schemas.toArray(new String[schemas.size()]); 503 } 504 505 @Override 506 @Deprecated 507 public String[] getDeclaredSchemas() { 508 return getSchemas(); 509 } 510 511 @Override 512 public boolean hasSchema(String schema) { 513 return schemas.contains(schema); 514 } 515 516 @Override 517 public Set<String> getFacets() { 518 return Collections.unmodifiableSet(facets); 519 } 520 521 @Override 522 public boolean hasFacet(String facet) { 523 return facets.contains(facet); 524 } 525 526 @Override 527 @Deprecated 528 public Set<String> getDeclaredFacets() { 529 return getFacets(); 530 } 531 532 @Override 533 public boolean addFacet(String facet) { 534 if (facet == null) { 535 throw new IllegalArgumentException("Null facet"); 536 } 537 if (facets.contains(facet)) { 538 return false; 539 } 540 TypeProvider typeProvider = Framework.getLocalService(SchemaManager.class); 541 CompositeType facetType = typeProvider.getFacet(facet); 542 if (facetType == null) { 543 throw new IllegalArgumentException("No such facet: " + facet); 544 } 545 // add it 546 facets.add(facet); 547 instanceFacets.add(facet); 548 schemas.addAll(Arrays.asList(facetType.getSchemaNames())); 549 return true; 550 } 551 552 @Override 553 public boolean removeFacet(String facet) { 554 if (facet == null) { 555 throw new IllegalArgumentException("Null facet"); 556 } 557 if (!instanceFacets.contains(facet)) { 558 return false; 559 } 560 // remove it 561 facets.remove(facet); 562 instanceFacets.remove(facet); 563 564 // find the schemas that were dropped 565 Set<String> droppedSchemas = new HashSet<String>(schemas); 566 schemas = computeSchemas(getDocumentType(), instanceFacets, isProxy()); 567 droppedSchemas.removeAll(schemas); 568 569 // clear these datamodels 570 for (String s : droppedSchemas) { 571 dataModels.remove(s); 572 } 573 574 return true; 575 } 576 577 protected static Set<String> inferFacets(Set<String> facets, DocumentType documentType) { 578 if (facets == null) { 579 facets = new HashSet<String>(); 580 if (documentType != null) { 581 facets.addAll(documentType.getFacets()); 582 } 583 } 584 return facets; 585 } 586 587 @Override 588 public String getId() { 589 return id; 590 } 591 592 @Override 593 public String getName() { 594 if (path != null) { 595 return path.lastSegment(); 596 } 597 return null; 598 } 599 600 @Override 601 public Long getPos() { 602 return pos; 603 } 604 605 /** 606 * Sets the document's position in its containing folder (if ordered). Used internally during construction. 607 * 608 * @param pos the position 609 * @since 6.0 610 */ 611 public void setPosInternal(Long pos) { 612 this.pos = pos; 613 } 614 615 @Override 616 public String getPathAsString() { 617 if (path != null) { 618 return path.toString(); 619 } 620 return null; 621 } 622 623 @Override 624 public Map<String, Object> getProperties(String schemaName) { 625 DataModel dm = getDataModel(schemaName); 626 return dm == null ? null : dm.getMap(); 627 } 628 629 @Override 630 public Object getProperty(String schemaName, String name) { 631 // look in prefetch 632 if (prefetch != null) { 633 Serializable value = prefetch.get(schemaName, name); 634 if (value != NULL) { 635 return value; 636 } 637 } 638 // look in datamodels 639 DataModel dm = dataModels.get(schemaName); 640 if (dm == null) { 641 dm = getDataModel(schemaName); 642 } 643 return dm == null ? null : dm.getData(name); 644 } 645 646 @Override 647 public void setPathInfo(String parentPath, String name) { 648 path = new Path(parentPath == null ? name : parentPath + '/' + name); 649 ref = new PathRef(parentPath, name); 650 } 651 652 protected String oldLockKey(Lock lock) { 653 if (lock == null) { 654 return null; 655 } 656 // return deprecated format, like "someuser:Nov 29, 2010" 657 String lockCreationDate = (lock.getCreated() == null) ? null 658 : DateFormat.getDateInstance(DateFormat.MEDIUM).format(new Date(lock.getCreated().getTimeInMillis())); 659 return lock.getOwner() + ':' + lockCreationDate; 660 } 661 662 @Override 663 @Deprecated 664 public String getLock() { 665 return oldLockKey(getLockInfo()); 666 } 667 668 @Override 669 public boolean isLocked() { 670 return getLockInfo() != null; 671 } 672 673 @Override 674 @Deprecated 675 public void setLock(String key) { 676 setLock(); 677 } 678 679 @Override 680 public void unlock() { 681 removeLock(); 682 } 683 684 @Override 685 public Lock setLock() { 686 Lock newLock = new RunWithCoreSession<Lock>() { 687 @Override 688 public Lock run() { 689 return session.setLock(ref); 690 } 691 }.execute(); 692 lock = newLock; 693 return lock; 694 } 695 696 @Override 697 public Lock getLockInfo() { 698 if (lock != LOCK_UNKNOWN) { 699 return lock; 700 } 701 // no lock if not tied to a session 702 CoreSession session = getCoreSession(); 703 if (session == null) { 704 return null; 705 } 706 lock = session.getLockInfo(ref); 707 return lock; 708 } 709 710 @Override 711 public Lock removeLock() { 712 Lock oldLock = new RunWithCoreSession<Lock>() { 713 @Override 714 public Lock run() { 715 return session.removeLock(ref); 716 } 717 }.execute(); 718 lock = null; 719 return oldLock; 720 } 721 722 @Override 723 public boolean isCheckedOut() { 724 if (!isStateLoaded) { 725 if (getCoreSession() == null) { 726 return true; 727 } 728 refresh(REFRESH_STATE, null); 729 } 730 return isCheckedOut; 731 } 732 733 @Override 734 public void checkOut() { 735 getCoreSession().checkOut(ref); 736 isStateLoaded = false; 737 // new version number, refresh content 738 refresh(REFRESH_CONTENT_IF_LOADED, null); 739 } 740 741 @Override 742 public DocumentRef checkIn(VersioningOption option, String description) { 743 DocumentRef versionRef = getCoreSession().checkIn(ref, option, description); 744 isStateLoaded = false; 745 // new version number, refresh content 746 refresh(REFRESH_CONTENT_IF_LOADED, null); 747 return versionRef; 748 } 749 750 @Override 751 public String getVersionLabel() { 752 if (detachedVersionLabel != null) { 753 return detachedVersionLabel; 754 } 755 if (getCoreSession() == null) { 756 return null; 757 } 758 return getCoreSession().getVersionLabel(this); 759 } 760 761 @Override 762 public String getVersionSeriesId() { 763 if (!isStateLoaded) { 764 refresh(REFRESH_STATE, null); 765 } 766 return versionSeriesId; 767 } 768 769 @Override 770 public boolean isLatestVersion() { 771 if (!isStateLoaded) { 772 refresh(REFRESH_STATE, null); 773 } 774 return isLatestVersion; 775 } 776 777 @Override 778 public boolean isMajorVersion() { 779 if (!isStateLoaded) { 780 refresh(REFRESH_STATE, null); 781 } 782 return isMajorVersion; 783 } 784 785 @Override 786 public boolean isLatestMajorVersion() { 787 if (!isStateLoaded) { 788 refresh(REFRESH_STATE, null); 789 } 790 return isLatestMajorVersion; 791 } 792 793 @Override 794 public boolean isVersionSeriesCheckedOut() { 795 if (!isStateLoaded) { 796 refresh(REFRESH_STATE, null); 797 } 798 return isVersionSeriesCheckedOut; 799 } 800 801 @Override 802 public String getCheckinComment() { 803 if (!isStateLoaded) { 804 refresh(REFRESH_STATE, null); 805 } 806 return checkinComment; 807 } 808 809 @Override 810 public ACP getACP() { 811 if (!isACPLoaded) { // lazy load 812 acp = new RunWithCoreSession<ACP>() { 813 @Override 814 public ACP run() { 815 return session.getACP(ref); 816 } 817 }.execute(); 818 isACPLoaded = true; 819 } 820 return acp; 821 } 822 823 @Override 824 public void setACP(final ACP acp, final boolean overwrite) { 825 new RunWithCoreSession<Object>() { 826 @Override 827 public Object run() { 828 session.setACP(ref, acp, overwrite); 829 return null; 830 } 831 }.execute(); 832 isACPLoaded = false; 833 } 834 835 @Override 836 public String getType() { 837 return typeName; 838 } 839 840 @Override 841 public void setProperties(String schemaName, Map<String, Object> data) { 842 DataModel dm = getDataModel(schemaName); 843 if (dm != null) { 844 dm.setMap(data); 845 clearPrefetch(schemaName); 846 } 847 } 848 849 @Override 850 public void setProperty(String schemaName, String name, Object value) { 851 DataModel dm = getDataModel(schemaName); 852 if (dm == null) { 853 return; 854 } 855 dm.setData(name, value); 856 clearPrefetch(schemaName); 857 } 858 859 @Override 860 public Path getPath() { 861 return path; 862 } 863 864 @Override 865 public DataModelMap getDataModels() { 866 return dataModels; 867 } 868 869 @Override 870 public boolean isFolder() { 871 return hasFacet(FacetNames.FOLDERISH); 872 } 873 874 @Override 875 public boolean isVersionable() { 876 return hasFacet(FacetNames.VERSIONABLE); 877 } 878 879 @Override 880 public boolean isDownloadable() { 881 if (hasFacet(FacetNames.DOWNLOADABLE)) { 882 // TODO find a better way to check size that does not depend on the 883 // document schema 884 Long size = (Long) getProperty("common", "size"); 885 if (size != null) { 886 return size.longValue() != 0; 887 } 888 } 889 return false; 890 } 891 892 @Override 893 public void accept(PropertyVisitor visitor, Object arg) { 894 for (DocumentPart dp : getParts()) { 895 ((DocumentPartImpl) dp).visitChildren(visitor, arg); 896 } 897 } 898 899 @Override 900 @SuppressWarnings("unchecked") 901 public <T> T getAdapter(Class<T> itf) { 902 T facet = (T) getAdapters().get(itf); 903 if (facet == null) { 904 facet = findAdapter(itf); 905 if (facet != null) { 906 adapters.put(itf, facet); 907 } 908 } 909 return facet; 910 } 911 912 /** 913 * Lazy initialization for adapters because they don't survive the serialization. 914 */ 915 private ArrayMap<Class<?>, Object> getAdapters() { 916 if (adapters == null) { 917 adapters = new ArrayMap<Class<?>, Object>(); 918 } 919 920 return adapters; 921 } 922 923 @Override 924 public <T> T getAdapter(Class<T> itf, boolean refreshCache) { 925 T facet; 926 927 if (!refreshCache) { 928 facet = getAdapter(itf); 929 } else { 930 facet = findAdapter(itf); 931 } 932 933 if (facet != null) { 934 getAdapters().put(itf, facet); 935 } 936 return facet; 937 } 938 939 @SuppressWarnings("unchecked") 940 private <T> T findAdapter(Class<T> itf) { 941 DocumentAdapterService svc = Framework.getService(DocumentAdapterService.class); 942 if (svc != null) { 943 DocumentAdapterDescriptor dae = svc.getAdapterDescriptor(itf); 944 if (dae != null) { 945 String facet = dae.getFacet(); 946 if (facet == null) { 947 // if no facet is specified, accept the adapter 948 return (T) dae.getFactory().getAdapter(this, itf); 949 } else if (hasFacet(facet)) { 950 return (T) dae.getFactory().getAdapter(this, itf); 951 } else { 952 // TODO: throw an exception 953 log.error("Document model cannot be adapted to " + itf + " because it has no facet " + facet); 954 } 955 } 956 } else { 957 log.warn("DocumentAdapterService not available. Cannot get document model adaptor for " + itf); 958 } 959 return null; 960 } 961 962 @Override 963 public boolean followTransition(final String transition) { 964 boolean res = new RunWithCoreSession<Boolean>() { 965 @Override 966 public Boolean run() { 967 return Boolean.valueOf(session.followTransition(ref, transition)); 968 } 969 }.execute().booleanValue(); 970 // Invalidate the prefetched value in this case. 971 if (res) { 972 currentLifeCycleState = null; 973 } 974 return res; 975 } 976 977 @Override 978 public Collection<String> getAllowedStateTransitions() { 979 return new RunWithCoreSession<Collection<String>>() { 980 @Override 981 public Collection<String> run() { 982 return session.getAllowedStateTransitions(ref); 983 } 984 }.execute(); 985 } 986 987 @Override 988 public String getCurrentLifeCycleState() { 989 if (currentLifeCycleState != null) { 990 return currentLifeCycleState; 991 } 992 // document was just created => not life cycle yet 993 if (sid == null) { 994 return null; 995 } 996 currentLifeCycleState = new RunWithCoreSession<String>() { 997 @Override 998 public String run() { 999 return session.getCurrentLifeCycleState(ref); 1000 } 1001 }.execute(); 1002 return currentLifeCycleState; 1003 } 1004 1005 @Override 1006 public String getLifeCyclePolicy() { 1007 if (lifeCyclePolicy != null) { 1008 return lifeCyclePolicy; 1009 } 1010 // String lifeCyclePolicy = null; 1011 lifeCyclePolicy = new RunWithCoreSession<String>() { 1012 @Override 1013 public String run() { 1014 return session.getLifeCyclePolicy(ref); 1015 } 1016 }.execute(); 1017 return lifeCyclePolicy; 1018 } 1019 1020 @Override 1021 public boolean isVersion() { 1022 return (flags & F_VERSION) != 0; 1023 } 1024 1025 @Override 1026 public boolean isProxy() { 1027 return (flags & F_PROXY) != 0; 1028 } 1029 1030 @Override 1031 public boolean isImmutable() { 1032 return (flags & F_IMMUTABLE) != 0; 1033 } 1034 1035 public void setIsVersion(boolean isVersion) { 1036 if (isVersion) { 1037 flags |= F_VERSION; 1038 } else { 1039 flags &= ~F_VERSION; 1040 } 1041 } 1042 1043 public void setIsProxy(boolean isProxy) { 1044 if (isProxy) { 1045 flags |= F_PROXY; 1046 } else { 1047 flags &= ~F_PROXY; 1048 } 1049 } 1050 1051 public void setIsImmutable(boolean isImmutable) { 1052 if (isImmutable) { 1053 flags |= F_IMMUTABLE; 1054 } else { 1055 flags &= ~F_IMMUTABLE; 1056 } 1057 } 1058 1059 @Override 1060 public boolean isDirty() { 1061 for (DataModel dm : dataModels.values()) { 1062 DocumentPart part = ((DataModelImpl) dm).getDocumentPart(); 1063 if (part.isDirty()) { 1064 return true; 1065 } 1066 } 1067 return false; 1068 } 1069 1070 @Override 1071 public ScopedMap getContextData() { 1072 return contextData; 1073 } 1074 1075 @Override 1076 public Serializable getContextData(ScopeType scope, String key) { 1077 return contextData.getScopedValue(scope, key); 1078 } 1079 1080 @Override 1081 public void putContextData(ScopeType scope, String key, Serializable value) { 1082 contextData.putScopedValue(scope, key, value); 1083 } 1084 1085 @Override 1086 public Serializable getContextData(String key) { 1087 return contextData.getScopedValue(key); 1088 } 1089 1090 @Override 1091 public void putContextData(String key, Serializable value) { 1092 contextData.putScopedValue(key, value); 1093 } 1094 1095 @Override 1096 public void copyContextData(DocumentModel otherDocument) { 1097 ScopedMap otherMap = otherDocument.getContextData(); 1098 if (otherMap != null) { 1099 contextData.putAll(otherMap); 1100 } 1101 } 1102 1103 @Override 1104 public void copyContent(DocumentModel sourceDoc) { 1105 computeFacetsAndSchemas(((DocumentModelImpl) sourceDoc).instanceFacets); 1106 DataModelMap newDataModels = new DataModelMapImpl(); 1107 for (String key : schemas) { 1108 DataModel oldDM = sourceDoc.getDataModel(key); 1109 DataModel newDM; 1110 if (oldDM != null) { 1111 newDM = cloneDataModel(oldDM); 1112 } else { 1113 // create an empty datamodel 1114 Schema schema = Framework.getService(SchemaManager.class).getSchema(key); 1115 newDM = new DataModelImpl(new DocumentPartImpl(schema)); 1116 } 1117 newDataModels.put(key, newDM); 1118 } 1119 dataModels = newDataModels; 1120 } 1121 1122 @SuppressWarnings("unchecked") 1123 public static Object cloneField(Field field, String key, Object value) { 1124 // key is unused 1125 Object clone; 1126 Type type = field.getType(); 1127 if (type.isSimpleType()) { 1128 // CLONE TODO 1129 if (value instanceof Calendar) { 1130 Calendar newValue = (Calendar) value; 1131 clone = newValue.clone(); 1132 } else { 1133 clone = value; 1134 } 1135 } else if (type.isListType()) { 1136 ListType ltype = (ListType) type; 1137 Field lfield = ltype.getField(); 1138 Type ftype = lfield.getType(); 1139 List<Object> list; 1140 if (value instanceof Object[]) { // these are stored as arrays 1141 list = Arrays.asList((Object[]) value); 1142 } else { 1143 list = (List<Object>) value; 1144 } 1145 if (ftype.isComplexType()) { 1146 List<Object> clonedList = new ArrayList<Object>(list.size()); 1147 for (Object o : list) { 1148 clonedList.add(cloneField(lfield, null, o)); 1149 } 1150 clone = clonedList; 1151 } else { 1152 Class<?> klass = JavaTypes.getClass(ftype); 1153 if (klass.isPrimitive()) { 1154 clone = PrimitiveArrays.toPrimitiveArray(list, klass); 1155 } else { 1156 clone = list.toArray((Object[]) Array.newInstance(klass, list.size())); 1157 } 1158 } 1159 } else { 1160 // complex type 1161 ComplexType ctype = (ComplexType) type; 1162 if (TypeConstants.isContentType(ctype)) { // if a blob 1163 Blob blob = (Blob) value; // TODO 1164 clone = blob; 1165 } else { 1166 // a map, regular complex type 1167 Map<String, Object> map = (Map<String, Object>) value; 1168 Map<String, Object> clonedMap = new HashMap<String, Object>(); 1169 for (Map.Entry<String, Object> entry : map.entrySet()) { 1170 Object v = entry.getValue(); 1171 String k = entry.getKey(); 1172 if (v == null) { 1173 continue; 1174 } 1175 clonedMap.put(k, cloneField(ctype.getField(k), k, v)); 1176 } 1177 clone = clonedMap; 1178 } 1179 } 1180 return clone; 1181 } 1182 1183 public static DataModel cloneDataModel(Schema schema, DataModel data) { 1184 DataModel dm = new DataModelImpl(schema.getName()); 1185 for (Field field : schema.getFields()) { 1186 String key = field.getName().getLocalName(); 1187 Object value; 1188 try { 1189 value = data.getData(key); 1190 } catch (PropertyException e1) { 1191 continue; 1192 } 1193 if (value == null) { 1194 continue; 1195 } 1196 Object clone = cloneField(field, key, value); 1197 dm.setData(key, clone); 1198 } 1199 return dm; 1200 } 1201 1202 public DataModel cloneDataModel(DataModel data) { 1203 TypeProvider typeProvider = Framework.getLocalService(SchemaManager.class); 1204 return cloneDataModel(typeProvider.getSchema(data.getSchema()), data); 1205 } 1206 1207 @Override 1208 public String getCacheKey() { 1209 // UUID - sessionId 1210 String key = id + '-' + sid + '-' + getPathAsString(); 1211 // assume the doc holds the dublincore schema (enough for us right now) 1212 if (hasSchema("dublincore")) { 1213 Calendar timeStamp = (Calendar) getProperty("dublincore", "modified"); 1214 if (timeStamp != null) { 1215 // remove milliseconds as they are not stored in some 1216 // databases, which could make the comparison fail just after a 1217 // document creation (see NXP-8783) 1218 timeStamp.set(Calendar.MILLISECOND, 0); 1219 key += '-' + String.valueOf(timeStamp.getTimeInMillis()); 1220 } 1221 } 1222 return key; 1223 } 1224 1225 @Override 1226 public String getRepositoryName() { 1227 return repositoryName; 1228 } 1229 1230 @Override 1231 public String getSourceId() { 1232 return sourceId; 1233 } 1234 1235 public boolean isSchemaLoaded(String name) { 1236 return dataModels.containsKey(name); 1237 } 1238 1239 @Override 1240 public boolean isPrefetched(String xpath) { 1241 return prefetch != null && prefetch.isPrefetched(xpath); 1242 } 1243 1244 @Override 1245 public boolean isPrefetched(String schemaName, String name) { 1246 return prefetch != null && prefetch.isPrefetched(schemaName, name); 1247 } 1248 1249 /** 1250 * Sets prefetch information. 1251 * <p> 1252 * INTERNAL: This method is not in the public interface. 1253 * 1254 * @since 5.5 1255 */ 1256 public void setPrefetch(Prefetch prefetch) { 1257 this.prefetch = prefetch; 1258 } 1259 1260 @Override 1261 public void prefetchCurrentLifecycleState(String lifecycle) { 1262 currentLifeCycleState = lifecycle; 1263 } 1264 1265 @Override 1266 public void prefetchLifeCyclePolicy(String lifeCyclePolicy) { 1267 this.lifeCyclePolicy = lifeCyclePolicy; 1268 } 1269 1270 @Override 1271 // need this for tree in RCP clients 1272 public boolean equals(Object obj) { 1273 if (obj == this) { 1274 return true; 1275 } 1276 if (obj instanceof DocumentModelImpl) { 1277 DocumentModel documentModel = (DocumentModel) obj; 1278 String id = documentModel.getId(); 1279 if (id != null) { 1280 return id.equals(this.id); 1281 } 1282 } 1283 return false; 1284 } 1285 1286 @Override 1287 public int hashCode() { 1288 return id == null ? 0 : id.hashCode(); 1289 } 1290 1291 @Override 1292 public String toString() { 1293 String title = id; 1294 if (getDataModels().containsKey("dublincore")) { 1295 title = getTitle(); 1296 } 1297 return getClass().getSimpleName() + '(' + id + ", path=" + path + ", title=" + title + ')'; 1298 } 1299 1300 @Override 1301 public <T extends Serializable> T getSystemProp(final String systemProperty, final Class<T> type) 1302 { 1303 return new RunWithCoreSession<T>() { 1304 @Override 1305 public T run() { 1306 return session.getDocumentSystemProp(ref, systemProperty, type); 1307 } 1308 }.execute(); 1309 } 1310 1311 @Override 1312 public boolean isLifeCycleLoaded() { 1313 return currentLifeCycleState != null; 1314 } 1315 1316 @Override 1317 public DocumentPart getPart(String schema) { 1318 DataModel dm = getDataModel(schema); 1319 if (dm != null) { 1320 return ((DataModelImpl) dm).getDocumentPart(); 1321 } 1322 return null; // TODO thrown an exception? 1323 } 1324 1325 @Override 1326 public DocumentPart[] getParts() { 1327 // DocumentType type = getDocumentType(); 1328 // type = Framework.getService(SchemaManager.class).getDocumentType( 1329 // getType()); 1330 // Collection<Schema> schemas = type.getSchemas(); 1331 // Set<String> allSchemas = getAllSchemas(); 1332 DocumentPart[] parts = new DocumentPart[schemas.size()]; 1333 int i = 0; 1334 for (String schema : schemas) { 1335 DataModel dm = getDataModel(schema); 1336 parts[i++] = ((DataModelImpl) dm).getDocumentPart(); 1337 } 1338 return parts; 1339 } 1340 1341 @Override 1342 public Property getProperty(String xpath) { 1343 if (xpath == null) { 1344 throw new PropertyNotFoundException("null", "Invalid null xpath"); 1345 } 1346 String cxpath = canonicalXPath(xpath); 1347 if (cxpath.isEmpty()) { 1348 throw new PropertyNotFoundException(xpath, "Schema not specified"); 1349 } 1350 String schemaName = getXPathSchemaName(cxpath, schemas, null); 1351 if (schemaName == null) { 1352 if (cxpath.indexOf(':') != -1) { 1353 throw new PropertyNotFoundException(xpath, "No such schema"); 1354 } else { 1355 throw new PropertyNotFoundException(xpath); 1356 } 1357 1358 } 1359 DocumentPart part = getPart(schemaName); 1360 if (part == null) { 1361 throw new PropertyNotFoundException(xpath); 1362 } 1363 // cut prefix 1364 String partPath = cxpath.substring(cxpath.indexOf(':') + 1); 1365 try { 1366 return part.resolvePath(partPath); 1367 } catch (PropertyNotFoundException e) { 1368 throw new PropertyNotFoundException(xpath, e.getDetail()); 1369 } 1370 } 1371 1372 public static String getXPathSchemaName(String xpath, Set<String> docSchemas, String[] returnName) { 1373 SchemaManager schemaManager = Framework.getLocalService(SchemaManager.class); 1374 // find first segment 1375 int i = xpath.indexOf('/'); 1376 String prop = i == -1 ? xpath : xpath.substring(0, i); 1377 int p = prop.indexOf(':'); 1378 if (p != -1) { 1379 // prefixed 1380 String prefix = prop.substring(0, p); 1381 Schema schema = schemaManager.getSchemaFromPrefix(prefix); 1382 if (schema == null) { 1383 // try directly with prefix as a schema name 1384 schema = schemaManager.getSchema(prefix); 1385 if (schema == null) { 1386 return null; 1387 } 1388 } 1389 if (returnName != null) { 1390 returnName[0] = prop.substring(p + 1); 1391 } 1392 return schema.getName(); 1393 } else { 1394 // unprefixed 1395 // search for the first matching schema having a property 1396 // with the same name as the first path segment 1397 for (String schemaName : docSchemas) { 1398 Schema schema = schemaManager.getSchema(schemaName); 1399 if (schema != null && schema.hasField(prop)) { 1400 if (returnName != null) { 1401 returnName[0] = prop; 1402 } 1403 return schema.getName(); 1404 } 1405 } 1406 return null; 1407 } 1408 } 1409 1410 @Override 1411 public Serializable getPropertyValue(String xpath) throws PropertyException { 1412 if (prefetch != null) { 1413 Serializable value = prefetch.get(xpath); 1414 if (value != NULL) { 1415 return value; 1416 } 1417 } 1418 return getProperty(xpath).getValue(); 1419 } 1420 1421 @Override 1422 public void setPropertyValue(String xpath, Serializable value) throws PropertyException { 1423 getProperty(xpath).setValue(value); 1424 clearPrefetchXPath(xpath); 1425 } 1426 1427 private void clearPrefetch(String schemaName) { 1428 if (prefetch != null) { 1429 prefetch.clearPrefetch(schemaName); 1430 if (prefetch.isEmpty()) { 1431 prefetch = null; 1432 } 1433 } 1434 } 1435 1436 protected void clearPrefetchXPath(String xpath) { 1437 if (prefetch != null) { 1438 String schemaName = prefetch.getXPathSchema(xpath, getDocumentType()); 1439 if (schemaName != null) { 1440 clearPrefetch(schemaName); 1441 } 1442 } 1443 } 1444 1445 @Override 1446 public DocumentModel clone() throws CloneNotSupportedException { 1447 DocumentModelImpl dm = (DocumentModelImpl) super.clone(); 1448 // dm.id =id; 1449 // dm.acp = acp; 1450 // dm.currentLifeCycleState = currentLifeCycleState; 1451 // dm.lifeCyclePolicy = lifeCyclePolicy; 1452 // dm.declaredSchemas = declaredSchemas; // schemas are immutable so we 1453 // don't clone the array 1454 // dm.flags = flags; 1455 // dm.repositoryName = repositoryName; 1456 // dm.ref = ref; 1457 // dm.parentRef = parentRef; 1458 // dm.path = path; // path is immutable 1459 // dm.isACPLoaded = isACPLoaded; 1460 // dm.prefetch = dm.prefetch; // prefetch can be shared 1461 // dm.lock = lock; 1462 // dm.sourceId =sourceId; 1463 // dm.sid = sid; 1464 // dm.type = type; 1465 dm.facets = new HashSet<String>(facets); // facets 1466 // should be 1467 // clones too - 1468 // they are not 1469 // immutable 1470 // context data is keeping contextual info so it is reseted 1471 dm.contextData = new ScopedMap(); 1472 1473 // copy parts 1474 dm.dataModels = new DataModelMapImpl(); 1475 for (Map.Entry<String, DataModel> entry : dataModels.entrySet()) { 1476 String key = entry.getKey(); 1477 DataModel data = entry.getValue(); 1478 DataModelImpl newData = new DataModelImpl(key, data.getMap()); 1479 dm.dataModels.put(key, newData); 1480 } 1481 return dm; 1482 } 1483 1484 @Override 1485 public void reset() { 1486 if (dataModels != null) { 1487 dataModels.clear(); 1488 } 1489 prefetch = null; 1490 isACPLoaded = false; 1491 acp = null; 1492 currentLifeCycleState = null; 1493 lifeCyclePolicy = null; 1494 } 1495 1496 @Override 1497 public void refresh() { 1498 detachedVersionLabel = null; 1499 1500 refresh(REFRESH_DEFAULT, null); 1501 } 1502 1503 @Override 1504 public void refresh(int refreshFlags, String[] schemas) { 1505 if (id == null) { 1506 // not yet saved 1507 return; 1508 } 1509 if ((refreshFlags & REFRESH_ACP_IF_LOADED) != 0 && isACPLoaded) { 1510 refreshFlags |= REFRESH_ACP; 1511 // we must not clean the REFRESH_ACP_IF_LOADED flag since it is 1512 // used 1513 // below on the client 1514 } 1515 1516 if ((refreshFlags & REFRESH_CONTENT_IF_LOADED) != 0) { 1517 refreshFlags |= REFRESH_CONTENT; 1518 Collection<String> keys = dataModels.keySet(); 1519 schemas = keys.toArray(new String[keys.size()]); 1520 } 1521 1522 DocumentModelRefresh refresh = getCoreSession().refreshDocument(ref, refreshFlags, schemas); 1523 1524 if ((refreshFlags & REFRESH_PREFETCH) != 0) { 1525 prefetch = refresh.prefetch; 1526 } 1527 if ((refreshFlags & REFRESH_STATE) != 0) { 1528 currentLifeCycleState = refresh.lifeCycleState; 1529 lifeCyclePolicy = refresh.lifeCyclePolicy; 1530 isCheckedOut = refresh.isCheckedOut; 1531 isLatestVersion = refresh.isLatestVersion; 1532 isMajorVersion = refresh.isMajorVersion; 1533 isLatestMajorVersion = refresh.isLatestMajorVersion; 1534 isVersionSeriesCheckedOut = refresh.isVersionSeriesCheckedOut; 1535 versionSeriesId = refresh.versionSeriesId; 1536 checkinComment = refresh.checkinComment; 1537 isStateLoaded = true; 1538 } 1539 acp = null; 1540 isACPLoaded = false; 1541 if ((refreshFlags & REFRESH_ACP) != 0) { 1542 acp = refresh.acp; 1543 isACPLoaded = true; 1544 } 1545 1546 if ((refreshFlags & (REFRESH_CONTENT | REFRESH_CONTENT_LAZY)) != 0) { 1547 dataModels.clear(); 1548 computeFacetsAndSchemas(refresh.instanceFacets); 1549 } 1550 if ((refreshFlags & REFRESH_CONTENT) != 0) { 1551 DocumentPart[] parts = refresh.documentParts; 1552 if (parts != null) { 1553 for (DocumentPart part : parts) { 1554 DataModelImpl dm = new DataModelImpl(part); 1555 dataModels.put(dm.getSchema(), dm); 1556 } 1557 } 1558 } 1559 } 1560 1561 /** 1562 * Recomputes all facets and schemas from the instance facets. 1563 * 1564 * @since 7.1 1565 */ 1566 protected void computeFacetsAndSchemas(Set<String> instanceFacets) { 1567 this.instanceFacets = instanceFacets; 1568 instanceFacetsOrig = new HashSet<>(instanceFacets); 1569 facets = new HashSet<>(instanceFacets); 1570 facets.addAll(getDocumentType().getFacets()); 1571 if (isImmutable()) { 1572 facets.add(FacetNames.IMMUTABLE); 1573 } 1574 schemas = computeSchemas(getDocumentType(), instanceFacets, isProxy()); 1575 schemasOrig = new HashSet<>(schemas); 1576 } 1577 1578 @Override 1579 public String getChangeToken() { 1580 if (!hasSchema("dublincore")) { 1581 return null; 1582 } 1583 try { 1584 Calendar modified = (Calendar) getPropertyValue("dc:modified"); 1585 if (modified != null) { 1586 return String.valueOf(modified.getTimeInMillis()); 1587 } 1588 } catch (PropertyException e) { 1589 log.error("Error while retrieving dc:modified", e); 1590 } 1591 return null; 1592 } 1593 1594 /** 1595 * Sets the document id. May be useful when detaching from a repo and attaching to another one or when unmarshalling 1596 * a documentModel from a XML or JSON representation 1597 * 1598 * @param id 1599 * @since 5.7.2 1600 */ 1601 public void setId(String id) { 1602 this.id = id; 1603 } 1604 1605 @Override 1606 public Map<String, String> getBinaryFulltext() { 1607 CoreSession session = getCoreSession(); 1608 if (session == null) { 1609 return null; 1610 } 1611 return session.getBinaryFulltext(ref); 1612 } 1613 1614 @Override 1615 public PropertyObjectResolver getObjectResolver(String xpath) { 1616 return DocumentPropertyObjectResolverImpl.create(this, xpath); 1617 } 1618 1619}