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.CoreSessionService; 056import org.nuxeo.ecm.core.api.DataModel; 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 Map<String, DataModel> 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 HashMap<>(); 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 Framework.getService(CoreSessionService.class).getCoreSession(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 Property getPropertyObject(String schema, String name) { 665 DocumentPart part = getPart(schema); 666 return part == null ? null : part.get(name); 667 } 668 669 @Override 670 public void setPathInfo(String parentPath, String name) { 671 path = new Path(parentPath == null ? name : parentPath + '/' + name); 672 ref = new PathRef(parentPath, name); 673 } 674 675 protected String oldLockKey(Lock lock) { 676 if (lock == null) { 677 return null; 678 } 679 // return deprecated format, like "someuser:Nov 29, 2010" 680 String lockCreationDate = (lock.getCreated() == null) ? null 681 : DateFormat.getDateInstance(DateFormat.MEDIUM).format(new Date(lock.getCreated().getTimeInMillis())); 682 return lock.getOwner() + ':' + lockCreationDate; 683 } 684 685 @Override 686 @Deprecated 687 public String getLock() { 688 return oldLockKey(getLockInfo()); 689 } 690 691 @Override 692 public boolean isLocked() { 693 return getLockInfo() != null; 694 } 695 696 @Override 697 @Deprecated 698 public void setLock(String key) { 699 setLock(); 700 } 701 702 @Override 703 public void unlock() { 704 removeLock(); 705 } 706 707 @Override 708 public Lock setLock() { 709 Lock newLock = new RunWithCoreSession<Lock>() { 710 @Override 711 public Lock run() { 712 return session.setLock(ref); 713 } 714 }.execute(); 715 lock = newLock; 716 return lock; 717 } 718 719 @Override 720 public Lock getLockInfo() { 721 if (lock != LOCK_UNKNOWN) { 722 return lock; 723 } 724 // no lock if not tied to a session 725 CoreSession session = getCoreSession(); 726 if (session == null) { 727 return null; 728 } 729 lock = session.getLockInfo(ref); 730 return lock; 731 } 732 733 @Override 734 public Lock removeLock() { 735 Lock oldLock = new RunWithCoreSession<Lock>() { 736 @Override 737 public Lock run() { 738 return session.removeLock(ref); 739 } 740 }.execute(); 741 lock = null; 742 return oldLock; 743 } 744 745 @Override 746 public boolean isCheckedOut() { 747 if (!isStateLoaded) { 748 if (getCoreSession() == null) { 749 return true; 750 } 751 refresh(REFRESH_STATE, null); 752 } 753 return isCheckedOut; 754 } 755 756 @Override 757 public void checkOut() { 758 getCoreSession().checkOut(ref); 759 isStateLoaded = false; 760 // new version number, refresh content 761 refresh(REFRESH_CONTENT_IF_LOADED, null); 762 } 763 764 @Override 765 public DocumentRef checkIn(VersioningOption option, String description) { 766 DocumentRef versionRef = getCoreSession().checkIn(ref, option, description); 767 isStateLoaded = false; 768 // new version number, refresh content 769 refresh(REFRESH_CONTENT_IF_LOADED, null); 770 return versionRef; 771 } 772 773 @Override 774 public String getVersionLabel() { 775 if (detachedVersionLabel != null) { 776 return detachedVersionLabel; 777 } 778 if (getCoreSession() == null) { 779 return null; 780 } 781 return getCoreSession().getVersionLabel(this); 782 } 783 784 @Override 785 public String getVersionSeriesId() { 786 if (!isStateLoaded) { 787 refresh(REFRESH_STATE, null); 788 } 789 return versionSeriesId; 790 } 791 792 @Override 793 public boolean isLatestVersion() { 794 if (!isStateLoaded) { 795 refresh(REFRESH_STATE, null); 796 } 797 return isLatestVersion; 798 } 799 800 @Override 801 public boolean isMajorVersion() { 802 if (!isStateLoaded) { 803 refresh(REFRESH_STATE, null); 804 } 805 return isMajorVersion; 806 } 807 808 @Override 809 public boolean isLatestMajorVersion() { 810 if (!isStateLoaded) { 811 refresh(REFRESH_STATE, null); 812 } 813 return isLatestMajorVersion; 814 } 815 816 @Override 817 public boolean isVersionSeriesCheckedOut() { 818 if (!isStateLoaded) { 819 refresh(REFRESH_STATE, null); 820 } 821 return isVersionSeriesCheckedOut; 822 } 823 824 @Override 825 public String getCheckinComment() { 826 if (!isStateLoaded) { 827 refresh(REFRESH_STATE, null); 828 } 829 return checkinComment; 830 } 831 832 @Override 833 public ACP getACP() { 834 if (!isACPLoaded) { // lazy load 835 acp = new RunWithCoreSession<ACP>() { 836 @Override 837 public ACP run() { 838 return session.getACP(ref); 839 } 840 }.execute(); 841 isACPLoaded = true; 842 } 843 return acp; 844 } 845 846 @Override 847 public void setACP(final ACP acp, final boolean overwrite) { 848 new RunWithCoreSession<Object>() { 849 @Override 850 public Object run() { 851 session.setACP(ref, acp, overwrite); 852 return null; 853 } 854 }.execute(); 855 isACPLoaded = false; 856 } 857 858 @Override 859 public String getType() { 860 return typeName; 861 } 862 863 @Override 864 public void setProperties(String schemaName, Map<String, Object> data) { 865 DataModel dm = getDataModel(schemaName); 866 if (dm != null) { 867 dm.setMap(data); 868 clearPrefetch(schemaName); 869 } 870 } 871 872 @Override 873 public void setProperty(String schemaName, String name, Object value) { 874 DataModel dm = getDataModel(schemaName); 875 if (dm == null) { 876 return; 877 } 878 dm.setData(name, value); 879 clearPrefetch(schemaName); 880 } 881 882 @Override 883 public Path getPath() { 884 return path; 885 } 886 887 @Override 888 public Map<String, DataModel> getDataModels() { 889 return dataModels; 890 } 891 892 @Override 893 public boolean isFolder() { 894 return hasFacet(FacetNames.FOLDERISH); 895 } 896 897 @Override 898 public boolean isVersionable() { 899 return hasFacet(FacetNames.VERSIONABLE); 900 } 901 902 @Override 903 public boolean isDownloadable() { 904 if (hasFacet(FacetNames.DOWNLOADABLE)) { 905 // TODO find a better way to check size that does not depend on the 906 // document schema 907 Long size = (Long) getProperty("common", "size"); 908 if (size != null) { 909 return size.longValue() != 0; 910 } 911 } 912 return false; 913 } 914 915 @Override 916 public void accept(PropertyVisitor visitor, Object arg) { 917 for (DocumentPart dp : getParts()) { 918 ((DocumentPartImpl) dp).visitChildren(visitor, arg); 919 } 920 } 921 922 @Override 923 @SuppressWarnings("unchecked") 924 public <T> T getAdapter(Class<T> itf) { 925 T facet = (T) getAdapters().get(itf); 926 if (facet == null) { 927 facet = findAdapter(itf); 928 if (facet != null) { 929 adapters.put(itf, facet); 930 } 931 } 932 return facet; 933 } 934 935 /** 936 * Lazy initialization for adapters because they don't survive the serialization. 937 */ 938 private ArrayMap<Class<?>, Object> getAdapters() { 939 if (adapters == null) { 940 adapters = new ArrayMap<Class<?>, Object>(); 941 } 942 943 return adapters; 944 } 945 946 @Override 947 public <T> T getAdapter(Class<T> itf, boolean refreshCache) { 948 T facet; 949 950 if (!refreshCache) { 951 facet = getAdapter(itf); 952 } else { 953 facet = findAdapter(itf); 954 } 955 956 if (facet != null) { 957 getAdapters().put(itf, facet); 958 } 959 return facet; 960 } 961 962 @SuppressWarnings("unchecked") 963 private <T> T findAdapter(Class<T> itf) { 964 DocumentAdapterService svc = Framework.getService(DocumentAdapterService.class); 965 if (svc != null) { 966 DocumentAdapterDescriptor dae = svc.getAdapterDescriptor(itf); 967 if (dae != null) { 968 String facet = dae.getFacet(); 969 if (facet == null) { 970 // if no facet is specified, accept the adapter 971 return (T) dae.getFactory().getAdapter(this, itf); 972 } else if (hasFacet(facet)) { 973 return (T) dae.getFactory().getAdapter(this, itf); 974 } else { 975 // TODO: throw an exception 976 log.error("Document model cannot be adapted to " + itf + " because it has no facet " + facet); 977 } 978 } 979 } else { 980 log.warn("DocumentAdapterService not available. Cannot get document model adaptor for " + itf); 981 } 982 return null; 983 } 984 985 @Override 986 public boolean followTransition(final String transition) { 987 boolean res = new RunWithCoreSession<Boolean>() { 988 @Override 989 public Boolean run() { 990 return Boolean.valueOf(session.followTransition(ref, transition)); 991 } 992 }.execute().booleanValue(); 993 // Invalidate the prefetched value in this case. 994 if (res) { 995 currentLifeCycleState = null; 996 } 997 return res; 998 } 999 1000 @Override 1001 public Collection<String> getAllowedStateTransitions() { 1002 return new RunWithCoreSession<Collection<String>>() { 1003 @Override 1004 public Collection<String> run() { 1005 return session.getAllowedStateTransitions(ref); 1006 } 1007 }.execute(); 1008 } 1009 1010 @Override 1011 public String getCurrentLifeCycleState() { 1012 if (currentLifeCycleState != null) { 1013 return currentLifeCycleState; 1014 } 1015 // document was just created => not life cycle yet 1016 if (sid == null) { 1017 return null; 1018 } 1019 currentLifeCycleState = new RunWithCoreSession<String>() { 1020 @Override 1021 public String run() { 1022 return session.getCurrentLifeCycleState(ref); 1023 } 1024 }.execute(); 1025 return currentLifeCycleState; 1026 } 1027 1028 @Override 1029 public String getLifeCyclePolicy() { 1030 if (lifeCyclePolicy != null) { 1031 return lifeCyclePolicy; 1032 } 1033 // String lifeCyclePolicy = null; 1034 lifeCyclePolicy = new RunWithCoreSession<String>() { 1035 @Override 1036 public String run() { 1037 return session.getLifeCyclePolicy(ref); 1038 } 1039 }.execute(); 1040 return lifeCyclePolicy; 1041 } 1042 1043 @Override 1044 public boolean isVersion() { 1045 return (flags & F_VERSION) != 0; 1046 } 1047 1048 @Override 1049 public boolean isProxy() { 1050 return (flags & F_PROXY) != 0; 1051 } 1052 1053 @Override 1054 public boolean isImmutable() { 1055 return (flags & F_IMMUTABLE) != 0; 1056 } 1057 1058 public void setIsVersion(boolean isVersion) { 1059 if (isVersion) { 1060 flags |= F_VERSION; 1061 } else { 1062 flags &= ~F_VERSION; 1063 } 1064 } 1065 1066 public void setIsProxy(boolean isProxy) { 1067 if (isProxy) { 1068 flags |= F_PROXY; 1069 } else { 1070 flags &= ~F_PROXY; 1071 } 1072 } 1073 1074 public void setIsImmutable(boolean isImmutable) { 1075 if (isImmutable) { 1076 flags |= F_IMMUTABLE; 1077 } else { 1078 flags &= ~F_IMMUTABLE; 1079 } 1080 } 1081 1082 @Override 1083 public boolean isDirty() { 1084 for (DataModel dm : dataModels.values()) { 1085 DocumentPart part = ((DataModelImpl) dm).getDocumentPart(); 1086 if (part.isDirty()) { 1087 return true; 1088 } 1089 } 1090 return false; 1091 } 1092 1093 @Override 1094 public ScopedMap getContextData() { 1095 return contextData; 1096 } 1097 1098 @Override 1099 public Serializable getContextData(ScopeType scope, String key) { 1100 return contextData.getScopedValue(scope, key); 1101 } 1102 1103 @Override 1104 public void putContextData(ScopeType scope, String key, Serializable value) { 1105 contextData.putScopedValue(scope, key, value); 1106 } 1107 1108 @Override 1109 public Serializable getContextData(String key) { 1110 return contextData.getScopedValue(key); 1111 } 1112 1113 @Override 1114 public void putContextData(String key, Serializable value) { 1115 contextData.putScopedValue(key, value); 1116 } 1117 1118 @Override 1119 public void copyContextData(DocumentModel otherDocument) { 1120 ScopedMap otherMap = otherDocument.getContextData(); 1121 if (otherMap != null) { 1122 contextData.putAll(otherMap); 1123 } 1124 } 1125 1126 @Override 1127 public void copyContent(DocumentModel sourceDoc) { 1128 computeFacetsAndSchemas(((DocumentModelImpl) sourceDoc).instanceFacets); 1129 Map<String, DataModel> newDataModels = new HashMap<>(); 1130 for (String key : schemas) { 1131 DataModel oldDM = sourceDoc.getDataModel(key); 1132 DataModel newDM; 1133 if (oldDM != null) { 1134 newDM = cloneDataModel(oldDM); 1135 } else { 1136 // create an empty datamodel 1137 Schema schema = Framework.getService(SchemaManager.class).getSchema(key); 1138 newDM = new DataModelImpl(new DocumentPartImpl(schema)); 1139 } 1140 newDataModels.put(key, newDM); 1141 } 1142 dataModels = newDataModels; 1143 } 1144 1145 @SuppressWarnings("unchecked") 1146 public static Object cloneField(Field field, String key, Object value) { 1147 // key is unused 1148 Object clone; 1149 Type type = field.getType(); 1150 if (type.isSimpleType()) { 1151 // CLONE TODO 1152 if (value instanceof Calendar) { 1153 Calendar newValue = (Calendar) value; 1154 clone = newValue.clone(); 1155 } else { 1156 clone = value; 1157 } 1158 } else if (type.isListType()) { 1159 ListType ltype = (ListType) type; 1160 Field lfield = ltype.getField(); 1161 Type ftype = lfield.getType(); 1162 List<Object> list; 1163 if (value instanceof Object[]) { // these are stored as arrays 1164 list = Arrays.asList((Object[]) value); 1165 } else { 1166 list = (List<Object>) value; 1167 } 1168 if (ftype.isComplexType()) { 1169 List<Object> clonedList = new ArrayList<Object>(list.size()); 1170 for (Object o : list) { 1171 clonedList.add(cloneField(lfield, null, o)); 1172 } 1173 clone = clonedList; 1174 } else { 1175 Class<?> klass = JavaTypes.getClass(ftype); 1176 if (klass.isPrimitive()) { 1177 clone = PrimitiveArrays.toPrimitiveArray(list, klass); 1178 } else { 1179 clone = list.toArray((Object[]) Array.newInstance(klass, list.size())); 1180 } 1181 } 1182 } else { 1183 // complex type 1184 ComplexType ctype = (ComplexType) type; 1185 if (TypeConstants.isContentType(ctype)) { // if a blob 1186 Blob blob = (Blob) value; // TODO 1187 clone = blob; 1188 } else { 1189 // a map, regular complex type 1190 Map<String, Object> map = (Map<String, Object>) value; 1191 Map<String, Object> clonedMap = new HashMap<String, Object>(); 1192 for (Map.Entry<String, Object> entry : map.entrySet()) { 1193 Object v = entry.getValue(); 1194 String k = entry.getKey(); 1195 if (v == null) { 1196 continue; 1197 } 1198 clonedMap.put(k, cloneField(ctype.getField(k), k, v)); 1199 } 1200 clone = clonedMap; 1201 } 1202 } 1203 return clone; 1204 } 1205 1206 public static DataModel cloneDataModel(Schema schema, DataModel data) { 1207 DataModel dm = new DataModelImpl(schema.getName()); 1208 for (Field field : schema.getFields()) { 1209 String key = field.getName().getLocalName(); 1210 Object value; 1211 try { 1212 value = data.getData(key); 1213 } catch (PropertyException e1) { 1214 continue; 1215 } 1216 if (value == null) { 1217 continue; 1218 } 1219 Object clone = cloneField(field, key, value); 1220 dm.setData(key, clone); 1221 } 1222 return dm; 1223 } 1224 1225 public DataModel cloneDataModel(DataModel data) { 1226 TypeProvider typeProvider = Framework.getLocalService(SchemaManager.class); 1227 return cloneDataModel(typeProvider.getSchema(data.getSchema()), data); 1228 } 1229 1230 @Override 1231 public String getCacheKey() { 1232 // UUID - sessionId 1233 String key = id + '-' + sid + '-' + getPathAsString(); 1234 // assume the doc holds the dublincore schema (enough for us right now) 1235 if (hasSchema("dublincore")) { 1236 Calendar timeStamp = (Calendar) getProperty("dublincore", "modified"); 1237 if (timeStamp != null) { 1238 // remove milliseconds as they are not stored in some 1239 // databases, which could make the comparison fail just after a 1240 // document creation (see NXP-8783) 1241 timeStamp.set(Calendar.MILLISECOND, 0); 1242 key += '-' + String.valueOf(timeStamp.getTimeInMillis()); 1243 } 1244 } 1245 return key; 1246 } 1247 1248 @Override 1249 public String getRepositoryName() { 1250 return repositoryName; 1251 } 1252 1253 @Override 1254 public String getSourceId() { 1255 return sourceId; 1256 } 1257 1258 public boolean isSchemaLoaded(String name) { 1259 return dataModels.containsKey(name); 1260 } 1261 1262 @Override 1263 public boolean isPrefetched(String xpath) { 1264 return prefetch != null && prefetch.isPrefetched(xpath); 1265 } 1266 1267 @Override 1268 public boolean isPrefetched(String schemaName, String name) { 1269 return prefetch != null && prefetch.isPrefetched(schemaName, name); 1270 } 1271 1272 /** 1273 * Sets prefetch information. 1274 * <p> 1275 * INTERNAL: This method is not in the public interface. 1276 * 1277 * @since 5.5 1278 */ 1279 public void setPrefetch(Prefetch prefetch) { 1280 this.prefetch = prefetch; 1281 } 1282 1283 @Override 1284 public void prefetchCurrentLifecycleState(String lifecycle) { 1285 currentLifeCycleState = lifecycle; 1286 } 1287 1288 @Override 1289 public void prefetchLifeCyclePolicy(String lifeCyclePolicy) { 1290 this.lifeCyclePolicy = lifeCyclePolicy; 1291 } 1292 1293 @Override 1294 // need this for tree in RCP clients 1295 public boolean equals(Object obj) { 1296 if (obj == this) { 1297 return true; 1298 } 1299 if (obj instanceof DocumentModelImpl) { 1300 DocumentModel documentModel = (DocumentModel) obj; 1301 String id = documentModel.getId(); 1302 if (id != null) { 1303 return id.equals(this.id); 1304 } 1305 } 1306 return false; 1307 } 1308 1309 @Override 1310 public int hashCode() { 1311 return id == null ? 0 : id.hashCode(); 1312 } 1313 1314 @Override 1315 public String toString() { 1316 String title = id; 1317 if (getDataModels().containsKey("dublincore")) { 1318 title = getTitle(); 1319 } 1320 return getClass().getSimpleName() + '(' + id + ", path=" + path + ", title=" + title + ')'; 1321 } 1322 1323 @Override 1324 public <T extends Serializable> T getSystemProp(final String systemProperty, final Class<T> type) { 1325 return new RunWithCoreSession<T>() { 1326 @Override 1327 public T run() { 1328 return session.getDocumentSystemProp(ref, systemProperty, type); 1329 } 1330 }.execute(); 1331 } 1332 1333 @Override 1334 public boolean isLifeCycleLoaded() { 1335 return currentLifeCycleState != null; 1336 } 1337 1338 @Override 1339 public DocumentPart getPart(String schema) { 1340 DataModel dm = getDataModel(schema); 1341 if (dm != null) { 1342 return ((DataModelImpl) dm).getDocumentPart(); 1343 } 1344 return null; // TODO thrown an exception? 1345 } 1346 1347 @Override 1348 public DocumentPart[] getParts() { 1349 // DocumentType type = getDocumentType(); 1350 // type = Framework.getService(SchemaManager.class).getDocumentType( 1351 // getType()); 1352 // Collection<Schema> schemas = type.getSchemas(); 1353 // Set<String> allSchemas = getAllSchemas(); 1354 DocumentPart[] parts = new DocumentPart[schemas.size()]; 1355 int i = 0; 1356 for (String schema : schemas) { 1357 DataModel dm = getDataModel(schema); 1358 parts[i++] = ((DataModelImpl) dm).getDocumentPart(); 1359 } 1360 return parts; 1361 } 1362 1363 @Override 1364 public Collection<Property> getPropertyObjects(String schema) { 1365 DocumentPart part = getPart(schema); 1366 return part == null ? Collections.emptyList() : part.getChildren(); 1367 } 1368 1369 @Override 1370 public Property getProperty(String xpath) { 1371 if (xpath == null) { 1372 throw new PropertyNotFoundException("null", "Invalid null xpath"); 1373 } 1374 String cxpath = canonicalXPath(xpath); 1375 if (cxpath.isEmpty()) { 1376 throw new PropertyNotFoundException(xpath, "Schema not specified"); 1377 } 1378 String schemaName = getXPathSchemaName(cxpath, schemas, null); 1379 if (schemaName == null) { 1380 if (cxpath.indexOf(':') != -1) { 1381 throw new PropertyNotFoundException(xpath, "No such schema"); 1382 } else { 1383 throw new PropertyNotFoundException(xpath); 1384 } 1385 1386 } 1387 DocumentPart part = getPart(schemaName); 1388 if (part == null) { 1389 throw new PropertyNotFoundException(xpath); 1390 } 1391 // cut prefix 1392 String partPath = cxpath.substring(cxpath.indexOf(':') + 1); 1393 try { 1394 return part.resolvePath(partPath); 1395 } catch (PropertyNotFoundException e) { 1396 throw new PropertyNotFoundException(xpath, e.getDetail()); 1397 } 1398 } 1399 1400 public static String getXPathSchemaName(String xpath, Set<String> docSchemas, String[] returnName) { 1401 SchemaManager schemaManager = Framework.getLocalService(SchemaManager.class); 1402 // find first segment 1403 int i = xpath.indexOf('/'); 1404 String prop = i == -1 ? xpath : xpath.substring(0, i); 1405 int p = prop.indexOf(':'); 1406 if (p != -1) { 1407 // prefixed 1408 String prefix = prop.substring(0, p); 1409 Schema schema = schemaManager.getSchemaFromPrefix(prefix); 1410 if (schema == null) { 1411 // try directly with prefix as a schema name 1412 schema = schemaManager.getSchema(prefix); 1413 if (schema == null) { 1414 return null; 1415 } 1416 } 1417 if (returnName != null) { 1418 returnName[0] = prop.substring(p + 1); 1419 } 1420 return schema.getName(); 1421 } else { 1422 // unprefixed 1423 // search for the first matching schema having a property 1424 // with the same name as the first path segment 1425 for (String schemaName : docSchemas) { 1426 Schema schema = schemaManager.getSchema(schemaName); 1427 if (schema != null && schema.hasField(prop)) { 1428 if (returnName != null) { 1429 returnName[0] = prop; 1430 } 1431 return schema.getName(); 1432 } 1433 } 1434 return null; 1435 } 1436 } 1437 1438 @Override 1439 public Serializable getPropertyValue(String xpath) throws PropertyException { 1440 if (prefetch != null) { 1441 Serializable value = prefetch.get(xpath); 1442 if (value != NULL) { 1443 return value; 1444 } 1445 } 1446 return getProperty(xpath).getValue(); 1447 } 1448 1449 @Override 1450 public void setPropertyValue(String xpath, Serializable value) throws PropertyException { 1451 getProperty(xpath).setValue(value); 1452 clearPrefetchXPath(xpath); 1453 } 1454 1455 private void clearPrefetch(String schemaName) { 1456 if (prefetch != null) { 1457 prefetch.clearPrefetch(schemaName); 1458 if (prefetch.isEmpty()) { 1459 prefetch = null; 1460 } 1461 } 1462 } 1463 1464 protected void clearPrefetchXPath(String xpath) { 1465 if (prefetch != null) { 1466 String schemaName = prefetch.getXPathSchema(xpath, getDocumentType()); 1467 if (schemaName != null) { 1468 clearPrefetch(schemaName); 1469 } 1470 } 1471 } 1472 1473 @Override 1474 public DocumentModel clone() throws CloneNotSupportedException { 1475 DocumentModelImpl dm = (DocumentModelImpl) super.clone(); 1476 // dm.id =id; 1477 // dm.acp = acp; 1478 // dm.currentLifeCycleState = currentLifeCycleState; 1479 // dm.lifeCyclePolicy = lifeCyclePolicy; 1480 // dm.declaredSchemas = declaredSchemas; // schemas are immutable so we 1481 // don't clone the array 1482 // dm.flags = flags; 1483 // dm.repositoryName = repositoryName; 1484 // dm.ref = ref; 1485 // dm.parentRef = parentRef; 1486 // dm.path = path; // path is immutable 1487 // dm.isACPLoaded = isACPLoaded; 1488 // dm.prefetch = dm.prefetch; // prefetch can be shared 1489 // dm.lock = lock; 1490 // dm.sourceId =sourceId; 1491 // dm.sid = sid; 1492 // dm.type = type; 1493 dm.facets = new HashSet<String>(facets); // facets 1494 // should be 1495 // clones too - 1496 // they are not 1497 // immutable 1498 // context data is keeping contextual info so it is reseted 1499 dm.contextData = new ScopedMap(); 1500 1501 // copy parts 1502 dm.dataModels = new HashMap<>(); 1503 for (Map.Entry<String, DataModel> entry : dataModels.entrySet()) { 1504 String key = entry.getKey(); 1505 DataModel data = entry.getValue(); 1506 DataModelImpl newData = new DataModelImpl(key, data.getMap()); 1507 for (String name:data.getDirtyFields()) { 1508 newData.setDirty(name); 1509 } 1510 dm.dataModels.put(key, newData); 1511 } 1512 return dm; 1513 } 1514 1515 @Override 1516 public void reset() { 1517 if (dataModels != null) { 1518 dataModels.clear(); 1519 } 1520 prefetch = null; 1521 isACPLoaded = false; 1522 acp = null; 1523 currentLifeCycleState = null; 1524 lifeCyclePolicy = null; 1525 } 1526 1527 @Override 1528 public void refresh() { 1529 detachedVersionLabel = null; 1530 1531 refresh(REFRESH_DEFAULT, null); 1532 } 1533 1534 @Override 1535 public void refresh(int refreshFlags, String[] schemas) { 1536 if (id == null) { 1537 // not yet saved 1538 return; 1539 } 1540 if ((refreshFlags & REFRESH_ACP_IF_LOADED) != 0 && isACPLoaded) { 1541 refreshFlags |= REFRESH_ACP; 1542 // we must not clean the REFRESH_ACP_IF_LOADED flag since it is 1543 // used 1544 // below on the client 1545 } 1546 1547 if ((refreshFlags & REFRESH_CONTENT_IF_LOADED) != 0) { 1548 refreshFlags |= REFRESH_CONTENT; 1549 Collection<String> keys = dataModels.keySet(); 1550 schemas = keys.toArray(new String[keys.size()]); 1551 } 1552 1553 DocumentModelRefresh refresh = getCoreSession().refreshDocument(ref, refreshFlags, schemas); 1554 1555 if ((refreshFlags & REFRESH_PREFETCH) != 0) { 1556 prefetch = refresh.prefetch; 1557 } 1558 if ((refreshFlags & REFRESH_STATE) != 0) { 1559 currentLifeCycleState = refresh.lifeCycleState; 1560 lifeCyclePolicy = refresh.lifeCyclePolicy; 1561 isCheckedOut = refresh.isCheckedOut; 1562 isLatestVersion = refresh.isLatestVersion; 1563 isMajorVersion = refresh.isMajorVersion; 1564 isLatestMajorVersion = refresh.isLatestMajorVersion; 1565 isVersionSeriesCheckedOut = refresh.isVersionSeriesCheckedOut; 1566 versionSeriesId = refresh.versionSeriesId; 1567 checkinComment = refresh.checkinComment; 1568 isStateLoaded = true; 1569 } 1570 acp = null; 1571 isACPLoaded = false; 1572 if ((refreshFlags & REFRESH_ACP) != 0) { 1573 acp = refresh.acp; 1574 isACPLoaded = true; 1575 } 1576 1577 if ((refreshFlags & (REFRESH_CONTENT | REFRESH_CONTENT_LAZY)) != 0) { 1578 dataModels.clear(); 1579 computeFacetsAndSchemas(refresh.instanceFacets); 1580 } 1581 if ((refreshFlags & REFRESH_CONTENT) != 0) { 1582 DocumentPart[] parts = refresh.documentParts; 1583 if (parts != null) { 1584 for (DocumentPart part : parts) { 1585 DataModelImpl dm = new DataModelImpl(part); 1586 dataModels.put(dm.getSchema(), dm); 1587 } 1588 } 1589 } 1590 } 1591 1592 /** 1593 * Recomputes all facets and schemas from the instance facets. 1594 * 1595 * @since 7.1 1596 */ 1597 protected void computeFacetsAndSchemas(Set<String> instanceFacets) { 1598 this.instanceFacets = instanceFacets; 1599 instanceFacetsOrig = new HashSet<>(instanceFacets); 1600 facets = new HashSet<>(instanceFacets); 1601 facets.addAll(getDocumentType().getFacets()); 1602 if (isImmutable()) { 1603 facets.add(FacetNames.IMMUTABLE); 1604 } 1605 schemas = computeSchemas(getDocumentType(), instanceFacets, isProxy()); 1606 schemasOrig = new HashSet<>(schemas); 1607 } 1608 1609 @Override 1610 public String getChangeToken() { 1611 if (!hasSchema("dublincore")) { 1612 return null; 1613 } 1614 try { 1615 Calendar modified = (Calendar) getPropertyValue("dc:modified"); 1616 if (modified != null) { 1617 return String.valueOf(modified.getTimeInMillis()); 1618 } 1619 } catch (PropertyException e) { 1620 log.error("Error while retrieving dc:modified", e); 1621 } 1622 return null; 1623 } 1624 1625 /** 1626 * Sets the document id. May be useful when detaching from a repo and attaching to another one or when unmarshalling 1627 * a documentModel from a XML or JSON representation 1628 * 1629 * @param id 1630 * @since 5.7.2 1631 */ 1632 public void setId(String id) { 1633 this.id = id; 1634 } 1635 1636 @Override 1637 public Map<String, String> getBinaryFulltext() { 1638 CoreSession session = getCoreSession(); 1639 if (session == null) { 1640 return null; 1641 } 1642 return session.getBinaryFulltext(ref); 1643 } 1644 1645 @Override 1646 public PropertyObjectResolver getObjectResolver(String xpath) { 1647 return DocumentPropertyObjectResolverImpl.create(this, xpath); 1648 } 1649 1650 /** 1651 * Replace the content by it's the reference if the document is live and not dirty. 1652 * 1653 * @see org.nuxeo.ecm.core.event.EventContext 1654 * @since 7.10 1655 */ 1656 private Object writeReplace() throws ObjectStreamException { 1657 if (!TransactionHelper.isTransactionActive()) { // protect from no transaction 1658 Transaction tx = TransactionHelper.suspendTransaction(); 1659 try { 1660 TransactionHelper.startTransaction(); 1661 try { 1662 return writeReplace(); 1663 } finally { 1664 TransactionHelper.commitOrRollbackTransaction(); 1665 } 1666 } finally { 1667 if (tx != null) { 1668 TransactionHelper.resumeTransaction(tx); 1669 } 1670 } 1671 } 1672 if (isDirty()) { 1673 return this; 1674 } 1675 CoreSession session = getCoreSession(); 1676 if (session == null) { 1677 return this; 1678 } 1679 if (!session.exists(ref)) { 1680 return this; 1681 } 1682 return new InstanceRef(this, session.getPrincipal()); 1683 } 1684 1685 /** 1686 * Legacy code: Explicitly detach the document to send the document as an event context parameter. 1687 * 1688 * @see org.nuxeo.ecm.core.event.EventContext 1689 * @since 7.10 1690 */ 1691 private void writeObject(ObjectOutputStream stream) throws IOException { 1692 CoreSession session = getCoreSession(); 1693 detach(session != null && ref != null && session.exists(ref)); 1694 stream.defaultWriteObject(); 1695 } 1696}