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