001/* 002 * (C) Copyright 2014 Nuxeo SA (http://nuxeo.com/) and contributors. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl-2.1.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * Florent Guillaume 016 */ 017package org.nuxeo.ecm.core.storage.dbs; 018 019import static java.lang.Boolean.TRUE; 020 021import java.io.Serializable; 022import java.util.ArrayList; 023import java.util.Arrays; 024import java.util.Calendar; 025import java.util.Collection; 026import java.util.Collections; 027import java.util.HashMap; 028import java.util.HashSet; 029import java.util.LinkedList; 030import java.util.List; 031import java.util.Map; 032import java.util.Set; 033import java.util.function.Consumer; 034 035import org.apache.commons.lang.StringUtils; 036import org.nuxeo.ecm.core.NXCore; 037import org.nuxeo.ecm.core.api.DocumentNotFoundException; 038import org.nuxeo.ecm.core.api.LifeCycleException; 039import org.nuxeo.ecm.core.api.Lock; 040import org.nuxeo.ecm.core.api.NuxeoException; 041import org.nuxeo.ecm.core.api.PropertyException; 042import org.nuxeo.ecm.core.api.model.DocumentPart; 043import org.nuxeo.ecm.core.api.model.Property; 044import org.nuxeo.ecm.core.api.model.PropertyNotFoundException; 045import org.nuxeo.ecm.core.api.model.ReadOnlyPropertyException; 046import org.nuxeo.ecm.core.api.model.impl.ComplexProperty; 047import org.nuxeo.ecm.core.blob.BlobManager; 048import org.nuxeo.ecm.core.lifecycle.LifeCycle; 049import org.nuxeo.ecm.core.lifecycle.LifeCycleService; 050import org.nuxeo.ecm.core.model.Document; 051import org.nuxeo.ecm.core.model.LockManager; 052import org.nuxeo.ecm.core.model.Session; 053import org.nuxeo.ecm.core.schema.DocumentType; 054import org.nuxeo.ecm.core.schema.SchemaManager; 055import org.nuxeo.ecm.core.schema.types.ComplexType; 056import org.nuxeo.ecm.core.schema.types.CompositeType; 057import org.nuxeo.ecm.core.schema.types.Field; 058import org.nuxeo.ecm.core.schema.types.Schema; 059import org.nuxeo.ecm.core.schema.types.Type; 060import org.nuxeo.ecm.core.storage.BaseDocument; 061import org.nuxeo.ecm.core.storage.State; 062import org.nuxeo.ecm.core.storage.sql.coremodel.SQLDocumentVersion.VersionNotModifiableException; 063import org.nuxeo.runtime.api.Framework; 064 065/** 066 * Implementation of a {@link Document} for Document-Based Storage. The document is stored as a JSON-like Map. The keys 067 * of the Map are the property names (including special names for system properties), and the values Map are 068 * Serializable values, either: 069 * <ul> 070 * <li>a scalar (String, Long, Double, Boolean, Calendar, Binary), 071 * <li>an array of scalars, 072 * <li>a List of Maps, recursively, 073 * <li>or another Map, recursively. 074 * </ul> 075 * An ACP value is stored as a list of maps. Each map has a keys for the ACL name and the actual ACL which is a list of 076 * ACEs. An ACE is a map having as keys username, permission, and grant. 077 * 078 * @since 5.9.4 079 */ 080public class DBSDocument extends BaseDocument<State> { 081 082 private static final Long ZERO = Long.valueOf(0); 083 084 public static final String SYSPROP_FULLTEXT_SIMPLE = "fulltextSimple"; 085 086 public static final String SYSPROP_FULLTEXT_BINARY = "fulltextBinary"; 087 088 public static final String SYSPROP_FULLTEXT_JOBID = "fulltextJobId"; 089 090 public static final String KEY_PREFIX = "ecm:"; 091 092 public static final String KEY_ID = "ecm:id"; 093 094 public static final String KEY_PARENT_ID = "ecm:parentId"; 095 096 public static final String KEY_ANCESTOR_IDS = "ecm:ancestorIds"; 097 098 public static final String KEY_PRIMARY_TYPE = "ecm:primaryType"; 099 100 public static final String KEY_MIXIN_TYPES = "ecm:mixinTypes"; 101 102 public static final String KEY_NAME = "ecm:name"; 103 104 public static final String KEY_POS = "ecm:pos"; 105 106 public static final String KEY_ACP = "ecm:acp"; 107 108 public static final String KEY_ACL_NAME = "name"; 109 110 public static final String KEY_PATH_INTERNAL = "ecm:__path"; 111 112 public static final String KEY_ACL = "acl"; 113 114 public static final String KEY_ACE_USER = "user"; 115 116 public static final String KEY_ACE_PERMISSION = "perm"; 117 118 public static final String KEY_ACE_GRANT = "grant"; 119 120 public static final String KEY_ACE_CREATOR = "creator"; 121 122 public static final String KEY_ACE_BEGIN = "begin"; 123 124 public static final String KEY_ACE_END = "end"; 125 126 public static final String KEY_ACE_STATUS = "status"; 127 128 public static final String KEY_READ_ACL = "ecm:racl"; 129 130 public static final String KEY_IS_CHECKED_IN = "ecm:isCheckedIn"; 131 132 public static final String KEY_IS_VERSION = "ecm:isVersion"; 133 134 public static final String KEY_IS_LATEST_VERSION = "ecm:isLatestVersion"; 135 136 public static final String KEY_IS_LATEST_MAJOR_VERSION = "ecm:isLatestMajorVersion"; 137 138 public static final String KEY_MAJOR_VERSION = "ecm:majorVersion"; 139 140 public static final String KEY_MINOR_VERSION = "ecm:minorVersion"; 141 142 public static final String KEY_VERSION_SERIES_ID = "ecm:versionSeriesId"; 143 144 public static final String KEY_VERSION_CREATED = "ecm:versionCreated"; 145 146 public static final String KEY_VERSION_LABEL = "ecm:versionLabel"; 147 148 public static final String KEY_VERSION_DESCRIPTION = "ecm:versionDescription"; 149 150 public static final String KEY_BASE_VERSION_ID = "ecm:baseVersionId"; 151 152 public static final String KEY_IS_PROXY = "ecm:isProxy"; 153 154 public static final String KEY_PROXY_TARGET_ID = "ecm:proxyTargetId"; 155 156 public static final String KEY_PROXY_VERSION_SERIES_ID = "ecm:proxyVersionSeriesId"; 157 158 public static final String KEY_PROXY_IDS = "ecm:proxyIds"; 159 160 public static final String KEY_LIFECYCLE_POLICY = "ecm:lifeCyclePolicy"; 161 162 public static final String KEY_LIFECYCLE_STATE = "ecm:lifeCycleState"; 163 164 public static final String KEY_LOCK_OWNER = "ecm:lockOwner"; 165 166 public static final String KEY_LOCK_CREATED = "ecm:lockCreated"; 167 168 public static final String KEY_BLOB_NAME = "name"; 169 170 public static final String KEY_BLOB_MIME_TYPE = "mime-type"; 171 172 public static final String KEY_BLOB_ENCODING = "encoding"; 173 174 public static final String KEY_BLOB_DIGEST = "digest"; 175 176 public static final String KEY_BLOB_LENGTH = "length"; 177 178 public static final String KEY_BLOB_DATA = "data"; 179 180 public static final String KEY_FULLTEXT_SIMPLE = "ecm:fulltextSimple"; 181 182 public static final String KEY_FULLTEXT_BINARY = "ecm:fulltextBinary"; 183 184 public static final String KEY_FULLTEXT_JOBID = "ecm:fulltextJobId"; 185 186 public static final String KEY_FULLTEXT_SCORE = "ecm:fulltextScore"; 187 188 public static final String APPLICATION_OCTET_STREAM = "application/octet-stream"; 189 190 protected final String id; 191 192 protected final DBSDocumentState docState; 193 194 protected final DocumentType type; 195 196 protected final List<Schema> proxySchemas; 197 198 protected final DBSSession session; 199 200 protected boolean readonly; 201 202 protected static final Map<String, String> systemPropNameMap; 203 204 static { 205 systemPropNameMap = new HashMap<String, String>(); 206 systemPropNameMap.put(SYSPROP_FULLTEXT_SIMPLE, KEY_FULLTEXT_SIMPLE); 207 systemPropNameMap.put(SYSPROP_FULLTEXT_BINARY, KEY_FULLTEXT_BINARY); 208 systemPropNameMap.put(SYSPROP_FULLTEXT_JOBID, KEY_FULLTEXT_JOBID); 209 } 210 211 public DBSDocument(DBSDocumentState docState, DocumentType type, DBSSession session, boolean readonly) { 212 // no state for NullDocument (parent of placeless children) 213 this.id = docState == null ? null : (String) docState.get(KEY_ID); 214 this.docState = docState; 215 this.type = type; 216 this.session = session; 217 if (docState != null && isProxy()) { 218 SchemaManager schemaManager = Framework.getService(SchemaManager.class); 219 proxySchemas = schemaManager.getProxySchemas(type.getName()); 220 } else { 221 proxySchemas = null; 222 } 223 this.readonly = readonly; 224 } 225 226 @Override 227 public DocumentType getType() { 228 return type; 229 } 230 231 @Override 232 public Session getSession() { 233 return session; 234 } 235 236 @Override 237 public String getRepositoryName() { 238 return session.getRepositoryName(); 239 } 240 241 @Override 242 protected List<Schema> getProxySchemas() { 243 return proxySchemas; 244 } 245 246 @Override 247 public String getUUID() { 248 return id; 249 } 250 251 @Override 252 public String getName() { 253 return docState.getName(); 254 } 255 256 @Override 257 public Long getPos() { 258 return (Long) docState.get(KEY_POS); 259 } 260 261 @Override 262 public Document getParent() { 263 if (isVersion()) { 264 Document workingCopy = session.getDocument(getVersionSeriesId()); 265 return workingCopy == null ? null : workingCopy.getParent(); 266 } 267 String parentId = docState.getParentId(); 268 return parentId == null ? null : session.getDocument(parentId); 269 } 270 271 @Override 272 public boolean isProxy() { 273 return TRUE.equals(docState.get(KEY_IS_PROXY)); 274 } 275 276 @Override 277 public boolean isVersion() { 278 return TRUE.equals(docState.get(KEY_IS_VERSION)); 279 } 280 281 @Override 282 public String getPath() { 283 if (isVersion()) { 284 Document workingCopy = session.getDocument(getVersionSeriesId()); 285 return workingCopy == null ? null : workingCopy.getPath(); 286 } 287 String name = getName(); 288 Document doc = getParent(); 289 if (doc == null) { 290 if ("".equals(name)) { 291 return "/"; // root 292 } else { 293 return name; // placeless, no slash 294 } 295 } 296 LinkedList<String> list = new LinkedList<String>(); 297 list.addFirst(name); 298 while (doc != null) { 299 list.addFirst(doc.getName()); 300 doc = doc.getParent(); 301 } 302 return StringUtils.join(list, '/'); 303 } 304 305 @Override 306 public Document getChild(String name) { 307 return session.getChild(id, name); 308 } 309 310 @Override 311 public List<Document> getChildren() { 312 if (!isFolder()) { 313 return Collections.emptyList(); 314 } 315 return session.getChildren(id); 316 } 317 318 @Override 319 public List<String> getChildrenIds() { 320 if (!isFolder()) { 321 return Collections.emptyList(); 322 } 323 return session.getChildrenIds(id); 324 } 325 326 @Override 327 public boolean hasChild(String name) { 328 if (!isFolder()) { 329 return false; 330 } 331 return session.hasChild(id, name); 332 } 333 334 @Override 335 public boolean hasChildren() { 336 if (!isFolder()) { 337 return false; 338 } 339 return session.hasChildren(id); 340 } 341 342 @Override 343 public Document addChild(String name, String typeName) { 344 if (!isFolder()) { 345 throw new IllegalArgumentException("Not a folder"); 346 } 347 return session.createChild(null, id, name, null, typeName); 348 } 349 350 @Override 351 public void orderBefore(String src, String dest) { 352 Document srcDoc = getChild(src); 353 if (srcDoc == null) { 354 throw new DocumentNotFoundException("Document " + this + " has no child: " + src); 355 } 356 Document destDoc; 357 if (dest == null) { 358 destDoc = null; 359 } else { 360 destDoc = getChild(dest); 361 if (destDoc == null) { 362 throw new DocumentNotFoundException("Document " + this + " has no child: " + dest); 363 } 364 } 365 session.orderBefore(id, srcDoc.getUUID(), destDoc == null ? null : destDoc.getUUID()); 366 } 367 368 // simple property only 369 @Override 370 public Serializable getPropertyValue(String name) { 371 DBSDocumentState docState = getStateMaybeProxyTarget(name); 372 return docState.get(name); 373 } 374 375 // simple property only 376 @Override 377 public void setPropertyValue(String name, Serializable value) { 378 DBSDocumentState docState = getStateMaybeProxyTarget(name); 379 docState.put(name, value); 380 } 381 382 // helpers for getValue / setValue 383 384 @Override 385 protected State getChild(State state, String name, Type type) { 386 return (State) state.get(name); 387 } 388 389 @Override 390 protected State getChildForWrite(State state, String name, Type type) throws PropertyException { 391 State child = getChild(state, name, type); 392 if (child == null) { 393 state.put(name, child = new State()); 394 } 395 return child; 396 } 397 398 @Override 399 protected List<State> getChildAsList(State state, String name) { 400 @SuppressWarnings("unchecked") 401 List<State> list = (List<State>) state.get(name); 402 if (list == null) { 403 list = new ArrayList<>(); 404 } 405 return list; 406 } 407 408 @Override 409 protected void updateList(State state, String name, List<Object> values, Field field) { 410 List<State> childStates = new ArrayList<>(values.size()); 411 for (Object v : values) { 412 State childState = new State(); 413 setValueComplex(childState, field, v); 414 childStates.add(childState); 415 } 416 state.put(name, (Serializable) childStates); 417 } 418 419 @Override 420 protected List<State> updateList(State state, String name, Property property) throws PropertyException { 421 Collection<Property> properties = property.getChildren(); 422 List<State> childStates = new ArrayList<>(properties.size()); 423 for (int i = 0; i < properties.size(); i++) { 424 childStates.add(new State()); 425 } 426 state.put(name, (Serializable) childStates); 427 return childStates; 428 } 429 430 @Override 431 public Object getValue(String xpath) throws PropertyException { 432 DBSDocumentState docState = getStateMaybeProxyTarget(xpath); 433 return getValueObject(docState.getState(), xpath); 434 } 435 436 @Override 437 public void setValue(String xpath, Object value) throws PropertyException { 438 DBSDocumentState docState = getStateMaybeProxyTarget(xpath); 439 // markDirty has to be called *before* we change the state 440 docState.markDirty(); 441 setValueObject(docState.getState(), xpath, value); 442 } 443 444 @Override 445 public void visitBlobs(Consumer<BlobAccessor> blobVisitor) throws PropertyException { 446 if (isProxy()) { 447 ((DBSDocument) getTargetDocument()).visitBlobs(blobVisitor); 448 // fall through for proxy schemas 449 } 450 Runnable markDirty = () -> docState.markDirty(); 451 visitBlobs(docState.getState(), blobVisitor, markDirty); 452 } 453 454 @Override 455 public Document checkIn(String label, String checkinComment) { 456 if (isProxy()) { 457 throw new NuxeoException("Proxies cannot be checked in"); 458 } else if (isVersion()) { 459 throw new VersionNotModifiableException(); 460 } else { 461 Document version = session.checkIn(id, label, checkinComment); 462 Framework.getService(BlobManager.class).freezeVersion(version); 463 return version; 464 } 465 } 466 467 @Override 468 public void checkOut() { 469 if (isProxy()) { 470 throw new NuxeoException("Proxies cannot be checked out"); 471 } else if (isVersion()) { 472 throw new VersionNotModifiableException(); 473 } else { 474 session.checkOut(id); 475 } 476 } 477 478 @Override 479 public List<String> getVersionsIds() { 480 return session.getVersionsIds(getVersionSeriesId()); 481 } 482 483 @Override 484 public List<Document> getVersions() { 485 List<String> ids = session.getVersionsIds(getVersionSeriesId()); 486 List<Document> versions = new ArrayList<Document>(); 487 for (String id : ids) { 488 versions.add(session.getDocument(id)); 489 } 490 return versions; 491 } 492 493 @Override 494 public Document getLastVersion() { 495 return session.getLastVersion(getVersionSeriesId()); 496 } 497 498 @Override 499 public Document getSourceDocument() { 500 if (isProxy()) { 501 return getTargetDocument(); 502 } else if (isVersion()) { 503 return getWorkingCopy(); 504 } else { 505 return this; 506 } 507 } 508 509 @Override 510 public void restore(Document version) { 511 if (!version.isVersion()) { 512 throw new NuxeoException("Cannot restore a non-version: " + version); 513 } 514 session.restoreVersion(this, version); 515 } 516 517 @Override 518 public Document getVersion(String label) { 519 DBSDocumentState state = session.getVersionByLabel(getVersionSeriesId(), label); 520 return session.getDocument(state); 521 } 522 523 @Override 524 public Document getBaseVersion() { 525 if (isProxy() || isVersion()) { 526 return null; 527 } else { 528 if (isCheckedOut()) { 529 return null; 530 } else { 531 String id = (String) docState.get(KEY_BASE_VERSION_ID); 532 if (id == null) { 533 // shouldn't happen 534 return null; 535 } 536 return session.getDocument(id); 537 } 538 } 539 } 540 541 @Override 542 public boolean isCheckedOut() { 543 if (isVersion()) { 544 return false; 545 } else { // also if isProxy() 546 return !TRUE.equals(docState.get(KEY_IS_CHECKED_IN)); 547 } 548 } 549 550 @Override 551 public String getVersionSeriesId() { 552 if (isProxy()) { 553 return (String) docState.get(KEY_PROXY_VERSION_SERIES_ID); 554 } else if (isVersion()) { 555 return (String) docState.get(KEY_VERSION_SERIES_ID); 556 } else { 557 return getUUID(); 558 } 559 } 560 561 @Override 562 public Calendar getVersionCreationDate() { 563 return (Calendar) docState.get(KEY_VERSION_CREATED); 564 } 565 566 @Override 567 public String getVersionLabel() { 568 return (String) docState.get(KEY_VERSION_LABEL); 569 } 570 571 @Override 572 public String getCheckinComment() { 573 return (String) docState.get(KEY_VERSION_DESCRIPTION); 574 } 575 576 @Override 577 public boolean isLatestVersion() { 578 if (isProxy() || isVersion()) { 579 return TRUE.equals(docState.get(KEY_IS_LATEST_VERSION)); 580 } else { 581 return false; 582 } 583 } 584 585 @Override 586 public boolean isMajorVersion() { 587 if (isProxy() || isVersion()) { 588 return ZERO.equals(docState.get(KEY_MINOR_VERSION)); 589 } else { 590 return false; 591 } 592 } 593 594 @Override 595 public boolean isLatestMajorVersion() { 596 if (isProxy() || isVersion()) { 597 return TRUE.equals(docState.get(KEY_IS_LATEST_MAJOR_VERSION)); 598 } else { 599 return false; 600 } 601 } 602 603 @Override 604 public boolean isVersionSeriesCheckedOut() { 605 if (isProxy() || isVersion()) { 606 Document workingCopy = getWorkingCopy(); 607 return workingCopy == null ? false : workingCopy.isCheckedOut(); 608 } else { 609 return isCheckedOut(); 610 } 611 } 612 613 @Override 614 public Document getWorkingCopy() { 615 if (isProxy() || isVersion()) { 616 String versionSeriesId = getVersionSeriesId(); 617 return versionSeriesId == null ? null : session.getDocument(versionSeriesId); 618 } else { 619 return this; 620 } 621 } 622 623 @Override 624 public boolean isFolder() { 625 return type == null // null document 626 || type.isFolder(); 627 } 628 629 @Override 630 public void setReadOnly(boolean readonly) { 631 this.readonly = readonly; 632 } 633 634 @Override 635 public boolean isReadOnly() { 636 return readonly; 637 } 638 639 @Override 640 public void remove() { 641 session.remove(id); 642 } 643 644 @Override 645 public String getLifeCycleState() { 646 return (String) docState.get(KEY_LIFECYCLE_STATE); 647 } 648 649 @Override 650 public void setCurrentLifeCycleState(String lifeCycleState) throws LifeCycleException { 651 docState.put(KEY_LIFECYCLE_STATE, lifeCycleState); 652 BlobManager blobManager = Framework.getService(BlobManager.class); 653 blobManager.notifyChanges(this, Collections.singleton(KEY_LIFECYCLE_STATE)); 654 } 655 656 @Override 657 public String getLifeCyclePolicy() { 658 return (String) docState.get(KEY_LIFECYCLE_POLICY); 659 } 660 661 @Override 662 public void setLifeCyclePolicy(String policy) throws LifeCycleException { 663 docState.put(KEY_LIFECYCLE_POLICY, policy); 664 BlobManager blobManager = Framework.getService(BlobManager.class); 665 blobManager.notifyChanges(this, Collections.singleton(KEY_LIFECYCLE_POLICY)); 666 } 667 668 // TODO generic 669 @Override 670 public void followTransition(String transition) throws LifeCycleException { 671 LifeCycleService service = NXCore.getLifeCycleService(); 672 if (service == null) { 673 throw new LifeCycleException("LifeCycleService not available"); 674 } 675 service.followTransition(this, transition); 676 } 677 678 // TODO generic 679 @Override 680 public Collection<String> getAllowedStateTransitions() throws LifeCycleException { 681 LifeCycleService service = NXCore.getLifeCycleService(); 682 if (service == null) { 683 throw new LifeCycleException("LifeCycleService not available"); 684 } 685 LifeCycle lifeCycle = service.getLifeCycleFor(this); 686 if (lifeCycle == null) { 687 return Collections.emptyList(); 688 } 689 return lifeCycle.getAllowedStateTransitionsFrom(getLifeCycleState()); 690 } 691 692 @Override 693 public void setSystemProp(String name, Serializable value) { 694 695 String propertyName = systemPropNameMap.get(name); 696 if (propertyName == null) { 697 throw new PropertyNotFoundException(name, "Unknown system property"); 698 } 699 setPropertyValue(propertyName, value); 700 } 701 702 @SuppressWarnings("unchecked") 703 @Override 704 public <T extends Serializable> T getSystemProp(String name, Class<T> type) { 705 String propertyName = systemPropNameMap.get(name); 706 if (propertyName == null) { 707 throw new PropertyNotFoundException(name, "Unknown system property: "); 708 } 709 Serializable value = getPropertyValue(propertyName); 710 if (value == null) { 711 if (type == Boolean.class) { 712 value = Boolean.FALSE; 713 } else if (type == Long.class) { 714 value = Long.valueOf(0); 715 } 716 } 717 return (T) value; 718 } 719 720 /** 721 * Checks if the given schema should be resolved on the proxy or the target. 722 */ 723 protected DBSDocumentState getStateMaybeProxyTarget(Type type) throws PropertyException { 724 if (isProxy() && !isSchemaForProxy(type.getName())) { 725 return ((DBSDocument) getTargetDocument()).docState; 726 } else { 727 return docState; 728 } 729 } 730 731 protected DBSDocumentState getStateMaybeProxyTarget(String xpath) { 732 if (isProxy() && !isSchemaForProxy(getSchema(xpath))) { 733 return ((DBSDocument) getTargetDocument()).docState; 734 } else { 735 return docState; 736 } 737 } 738 739 protected boolean isSchemaForProxy(String schema) { 740 SchemaManager schemaManager = Framework.getLocalService(SchemaManager.class); 741 return schemaManager.isProxySchema(schema, getType().getName()); 742 } 743 744 protected String getSchema(String xpath) { 745 int p = xpath.indexOf(':'); 746 if (p == -1) { 747 throw new PropertyNotFoundException(xpath, "Schema not specified"); 748 } 749 String prefix = xpath.substring(0, p); 750 SchemaManager schemaManager = Framework.getLocalService(SchemaManager.class); 751 Schema schema = schemaManager.getSchemaFromPrefix(prefix); 752 if (schema == null) { 753 schema = schemaManager.getSchema(prefix); 754 if (schema == null) { 755 throw new PropertyNotFoundException(xpath, "No schema for prefix"); 756 } 757 } 758 return schema.getName(); 759 } 760 761 @Override 762 public void readDocumentPart(DocumentPart dp) throws PropertyException { 763 DBSDocumentState docState = getStateMaybeProxyTarget(dp.getType()); 764 readComplexProperty(docState.getState(), (ComplexProperty) dp); 765 } 766 767 @Override 768 protected String internalName(String name) { 769 switch (name) { 770 case "major_version": 771 return KEY_MAJOR_VERSION; 772 case "minor_version": 773 return KEY_MINOR_VERSION; 774 } 775 return name; 776 } 777 778 @Override 779 public Map<String, Serializable> readPrefetch(ComplexType complexType, Set<String> xpaths) 780 throws PropertyException { 781 DBSDocumentState docState = getStateMaybeProxyTarget(complexType); 782 return readPrefetch(docState.getState(), complexType, xpaths); 783 } 784 785 @Override 786 public boolean writeDocumentPart(DocumentPart dp, WriteContext writeContext) throws PropertyException { 787 final DBSDocumentState docState = getStateMaybeProxyTarget(dp.getType()); 788 // markDirty has to be called *before* we change the state 789 docState.markDirty(); 790 boolean changed = writeComplexProperty(docState.getState(), (ComplexProperty) dp, writeContext); 791 clearDirtyFlags(dp); 792 return changed; 793 } 794 795 @Override 796 public Set<String> getAllFacets() { 797 Set<String> facets = new HashSet<String>(getType().getFacets()); 798 facets.addAll(Arrays.asList(getFacets())); 799 return facets; 800 } 801 802 @Override 803 public String[] getFacets() { 804 Object[] mixins = (Object[]) docState.get(KEY_MIXIN_TYPES); 805 if (mixins == null) { 806 return EMPTY_STRING_ARRAY; 807 } else { 808 String[] res = new String[mixins.length]; 809 System.arraycopy(mixins, 0, res, 0, mixins.length); 810 return res; 811 } 812 } 813 814 @Override 815 public boolean hasFacet(String facet) { 816 return getAllFacets().contains(facet); 817 } 818 819 @Override 820 public boolean addFacet(String facet) { 821 if (getType().getFacets().contains(facet)) { 822 return false; // already present in type 823 } 824 Object[] mixins = (Object[]) docState.get(KEY_MIXIN_TYPES); 825 if (mixins == null) { 826 mixins = new Object[] { facet }; 827 } else { 828 List<Object> list = Arrays.asList(mixins); 829 if (list.contains(facet)) { 830 return false; // already present in doc 831 } 832 list = new ArrayList<Object>(list); 833 list.add(facet); 834 mixins = list.toArray(new Object[list.size()]); 835 } 836 docState.put(KEY_MIXIN_TYPES, mixins); 837 return true; 838 } 839 840 @Override 841 public boolean removeFacet(String facet) { 842 Object[] mixins = (Object[]) docState.get(KEY_MIXIN_TYPES); 843 if (mixins == null) { 844 return false; 845 } 846 List<Object> list = new ArrayList<Object>(Arrays.asList(mixins)); 847 if (!list.remove(facet)) { 848 return false; // not present in doc 849 } 850 mixins = list.toArray(new Object[list.size()]); 851 if (mixins.length == 0) { 852 mixins = null; 853 } 854 docState.put(KEY_MIXIN_TYPES, mixins); 855 // remove the fields from the facet 856 SchemaManager schemaManager = Framework.getLocalService(SchemaManager.class); 857 CompositeType ft = schemaManager.getFacet(facet); 858 for (Field field : ft.getFields()) { 859 String name = field.getName().getPrefixedName(); 860 if (docState.containsKey(name)) { 861 docState.put(name, null); 862 } 863 } 864 return true; 865 } 866 867 @Override 868 public Document getTargetDocument() { 869 if (isProxy()) { 870 String targetId = (String) docState.get(KEY_PROXY_TARGET_ID); 871 return session.getDocument(targetId); 872 } else { 873 return null; 874 } 875 } 876 877 @Override 878 public void setTargetDocument(Document target) { 879 if (isProxy()) { 880 if (isReadOnly()) { 881 throw new ReadOnlyPropertyException("Cannot write proxy: " + this); 882 } 883 if (!target.getVersionSeriesId().equals(getVersionSeriesId())) { 884 throw new ReadOnlyPropertyException("Cannot set proxy target to different version series"); 885 } 886 session.setProxyTarget(this, target); 887 } else { 888 throw new NuxeoException("Cannot set proxy target on non-proxy"); 889 } 890 } 891 892 @Override 893 protected Lock getDocumentLock() { 894 String owner = (String) docState.get(KEY_LOCK_OWNER); 895 if (owner == null) { 896 return null; 897 } 898 Calendar created = (Calendar) docState.get(KEY_LOCK_CREATED); 899 return new Lock(owner, created); 900 } 901 902 @Override 903 protected Lock setDocumentLock(Lock lock) { 904 String owner = (String) docState.get(KEY_LOCK_OWNER); 905 if (owner != null) { 906 // return old lock 907 Calendar created = (Calendar) docState.get(KEY_LOCK_CREATED); 908 return new Lock(owner, created); 909 } 910 docState.put(KEY_LOCK_OWNER, lock.getOwner()); 911 docState.put(KEY_LOCK_CREATED, lock.getCreated()); 912 return null; 913 } 914 915 @Override 916 protected Lock removeDocumentLock(String owner) { 917 String oldOwner = (String) docState.get(KEY_LOCK_OWNER); 918 if (oldOwner == null) { 919 // no previous lock 920 return null; 921 } 922 Calendar oldCreated = (Calendar) docState.get(KEY_LOCK_CREATED); 923 if (!LockManager.canLockBeRemoved(oldOwner, owner)) { 924 // existing mismatched lock, flag failure 925 return new Lock(oldOwner, oldCreated, true); 926 } 927 // remove lock 928 docState.put(KEY_LOCK_OWNER, null); 929 docState.put(KEY_LOCK_CREATED, null); 930 // return old lock 931 return new Lock(oldOwner, oldCreated); 932 } 933 934 @Override 935 public String toString() { 936 return getClass().getSimpleName() + '(' + getName() + ',' + getUUID() + ')'; 937 } 938 939 @Override 940 public boolean equals(Object other) { 941 if (other == this) { 942 return true; 943 } 944 if (other == null) { 945 return false; 946 } 947 if (other.getClass() == getClass()) { 948 return equals((DBSDocument) other); 949 } 950 return false; 951 } 952 953 private boolean equals(DBSDocument other) { 954 return id.equals(other.id); 955 } 956 957 @Override 958 public int hashCode() { 959 return id.hashCode(); 960 } 961 962}