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