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