001/* 002 * Copyright (c) 2006-2014 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the Eclipse Public License v1.0 006 * which accompanies this distribution, and is available at 007 * http://www.eclipse.org/legal/epl-v10.html 008 * 009 * Contributors: 010 * Florent Guillaume 011 */ 012package org.nuxeo.ecm.core.storage.sql.coremodel; 013 014import java.io.Serializable; 015import java.util.ArrayList; 016import java.util.Calendar; 017import java.util.Collection; 018import java.util.Collections; 019import java.util.HashMap; 020import java.util.List; 021import java.util.Map; 022import java.util.Set; 023import java.util.function.Consumer; 024 025import org.nuxeo.ecm.core.NXCore; 026import org.nuxeo.ecm.core.api.DocumentNotFoundException; 027import org.nuxeo.ecm.core.api.LifeCycleException; 028import org.nuxeo.ecm.core.api.Lock; 029import org.nuxeo.ecm.core.api.NuxeoException; 030import org.nuxeo.ecm.core.api.PropertyException; 031import org.nuxeo.ecm.core.api.model.DocumentPart; 032import org.nuxeo.ecm.core.api.model.Property; 033import org.nuxeo.ecm.core.api.model.PropertyNotFoundException; 034import org.nuxeo.ecm.core.api.model.impl.ComplexProperty; 035import org.nuxeo.ecm.core.blob.BlobManager; 036import org.nuxeo.ecm.core.lifecycle.LifeCycle; 037import org.nuxeo.ecm.core.lifecycle.LifeCycleService; 038import org.nuxeo.ecm.core.model.Document; 039import org.nuxeo.ecm.core.schema.DocumentType; 040import org.nuxeo.ecm.core.schema.SchemaManager; 041import org.nuxeo.ecm.core.schema.types.ComplexType; 042import org.nuxeo.ecm.core.schema.types.Field; 043import org.nuxeo.ecm.core.schema.types.ListType; 044import org.nuxeo.ecm.core.schema.types.Schema; 045import org.nuxeo.ecm.core.schema.types.Type; 046import org.nuxeo.ecm.core.storage.BaseDocument; 047import org.nuxeo.ecm.core.storage.sql.Model; 048import org.nuxeo.ecm.core.storage.sql.Node; 049import org.nuxeo.runtime.api.Framework; 050 051public class SQLDocumentLive extends BaseDocument<Node>implements SQLDocument { 052 053 protected final Node node; 054 055 protected final Type type; 056 057 protected SQLSession session; 058 059 /** Proxy-induced types. */ 060 protected final List<Schema> proxySchemas; 061 062 /** 063 * Read-only flag, used to allow/disallow writes on versions. 064 */ 065 protected boolean readonly; 066 067 protected SQLDocumentLive(Node node, ComplexType type, SQLSession session, boolean readonly) { 068 this.node = node; 069 this.type = type; 070 this.session = session; 071 if (node != null && node.isProxy()) { 072 SchemaManager schemaManager = Framework.getLocalService(SchemaManager.class); 073 proxySchemas = schemaManager.getProxySchemas(type.getName()); 074 } else { 075 proxySchemas = null; 076 } 077 this.readonly = readonly; 078 } 079 080 @Override 081 public void setReadOnly(boolean readonly) { 082 this.readonly = readonly; 083 } 084 085 @Override 086 public boolean isReadOnly() { 087 return readonly; 088 } 089 090 @Override 091 public Node getNode() { 092 return node; 093 } 094 095 @Override 096 public String getName() { 097 return getNode() == null ? null : getNode().getName(); 098 } 099 100 @Override 101 public Long getPos() { 102 return getNode().getPos(); 103 } 104 105 /* 106 * ----- org.nuxeo.ecm.core.model.Document ----- 107 */ 108 109 @Override 110 public DocumentType getType() { 111 return (DocumentType) type; 112 } 113 114 @Override 115 public SQLSession getSession() { 116 return session; 117 } 118 119 @Override 120 public boolean isFolder() { 121 return type == null // null document 122 || ((DocumentType) type).isFolder(); 123 } 124 125 @Override 126 public String getUUID() { 127 return session.idToString(getNode().getId()); 128 } 129 130 @Override 131 public Document getParent() { 132 return session.getParent(getNode()); 133 } 134 135 @Override 136 public String getPath() { 137 return session.getPath(getNode()); 138 } 139 140 @Override 141 public boolean isProxy() { 142 return false; 143 } 144 145 @Override 146 public String getRepositoryName() { 147 return session.getRepositoryName(); 148 } 149 150 @Override 151 protected List<Schema> getProxySchemas() { 152 return proxySchemas; 153 } 154 155 @Override 156 public void remove() { 157 session.remove(getNode()); 158 } 159 160 /** 161 * Reads into the {@link DocumentPart} the values from this {@link SQLDocument}. 162 */ 163 @Override 164 public void readDocumentPart(DocumentPart dp) throws PropertyException { 165 readComplexProperty(getNode(), (ComplexProperty) dp); 166 } 167 168 @Override 169 public Map<String, Serializable> readPrefetch(ComplexType complexType, Set<String> xpaths) 170 throws PropertyException { 171 return readPrefetch(getNode(), complexType, xpaths); 172 } 173 174 @Override 175 public boolean writeDocumentPart(DocumentPart dp, WriteContext writeContext) throws PropertyException { 176 boolean changed = writeComplexProperty(getNode(), (ComplexProperty) dp, writeContext); 177 clearDirtyFlags(dp); 178 return changed; 179 } 180 181 @Override 182 protected Node getChild(Node node, String name, Type type) throws PropertyException { 183 return session.getChildProperty(node, name, type.getName()); 184 } 185 186 @Override 187 protected Node getChildForWrite(Node node, String name, Type type) throws PropertyException { 188 return session.getChildPropertyForWrite(node, name, type.getName()); 189 } 190 191 @Override 192 protected List<Node> getChildAsList(Node node, String name) throws PropertyException { 193 return session.getComplexList(node, name); 194 } 195 196 @Override 197 protected void updateList(Node node, String name, List<Object> values, Field field) throws PropertyException { 198 List<Node> childNodes = getChildAsList(node, name); 199 int oldSize = childNodes.size(); 200 int newSize = values.size(); 201 // remove extra list elements 202 if (oldSize > newSize) { 203 for (int i = oldSize - 1; i >= newSize; i--) { 204 session.removeProperty(childNodes.remove(i)); 205 } 206 } 207 // add new list elements 208 if (oldSize < newSize) { 209 String typeName = field.getType().getName(); 210 for (int i = oldSize; i < newSize; i++) { 211 Node childNode = session.addChildProperty(node, name, Long.valueOf(i), typeName); 212 childNodes.add(childNode); 213 } 214 } 215 // write values 216 int i = 0; 217 for (Object v : values) { 218 Node childNode = childNodes.get(i++); 219 setValueComplex(childNode, field, v); 220 } 221 } 222 223 @Override 224 protected List<Node> updateList(Node node, String name, Property property) throws PropertyException { 225 Collection<Property> properties = property.getChildren(); 226 List<Node> childNodes = getChildAsList(node, name); 227 int oldSize = childNodes.size(); 228 int newSize = properties.size(); 229 // remove extra list elements 230 if (oldSize > newSize) { 231 for (int i = oldSize - 1; i >= newSize; i--) { 232 session.removeProperty(childNodes.remove(i)); 233 } 234 } 235 // add new list elements 236 if (oldSize < newSize) { 237 String typeName = ((ListType) property.getType()).getFieldType().getName(); 238 for (int i = oldSize; i < newSize; i++) { 239 Node childNode = session.addChildProperty(node, name, Long.valueOf(i), typeName); 240 childNodes.add(childNode); 241 } 242 } 243 return childNodes; 244 } 245 246 @Override 247 protected String internalName(String name) { 248 return name; 249 } 250 251 @Override 252 public Object getValue(String xpath) throws PropertyException { 253 return getValueObject(getNode(), xpath); 254 } 255 256 @Override 257 public void setValue(String xpath, Object value) throws PropertyException { 258 setValueObject(getNode(), xpath, value); 259 } 260 261 @Override 262 public void visitBlobs(Consumer<BlobAccessor> blobVisitor) throws PropertyException { 263 visitBlobs(getNode(), blobVisitor, NO_DIRTY); 264 } 265 266 @Override 267 public Serializable getPropertyValue(String name) { 268 return getNode().getSimpleProperty(name).getValue(); 269 } 270 271 @Override 272 public void setPropertyValue(String name, Serializable value) { 273 getNode().setSimpleProperty(name, value); 274 } 275 276 protected static final Map<String, String> systemPropNameMap; 277 278 static { 279 systemPropNameMap = new HashMap<String, String>(); 280 systemPropNameMap.put(FULLTEXT_JOBID_SYS_PROP, Model.FULLTEXT_JOBID_PROP); 281 } 282 283 @Override 284 public void setSystemProp(String name, Serializable value) { 285 String propertyName; 286 if (name.startsWith(SIMPLE_TEXT_SYS_PROP)) { 287 propertyName = name.replace(SIMPLE_TEXT_SYS_PROP, Model.FULLTEXT_SIMPLETEXT_PROP); 288 } else if (name.startsWith(BINARY_TEXT_SYS_PROP)) { 289 propertyName = name.replace(BINARY_TEXT_SYS_PROP, Model.FULLTEXT_BINARYTEXT_PROP); 290 } else { 291 propertyName = systemPropNameMap.get(name); 292 } 293 if (propertyName == null) { 294 throw new PropertyNotFoundException(name, "Unknown system property"); 295 } 296 setPropertyValue(propertyName, value); 297 } 298 299 @Override 300 @SuppressWarnings("unchecked") 301 public <T extends Serializable> T getSystemProp(String name, Class<T> type) { 302 String propertyName = systemPropNameMap.get(name); 303 if (propertyName == null) { 304 throw new PropertyNotFoundException(name, "Unknown system property"); 305 } 306 Serializable value = getPropertyValue(propertyName); 307 if (value == null) { 308 if (type == Boolean.class) { 309 value = Boolean.FALSE; 310 } else if (type == Long.class) { 311 value = Long.valueOf(0); 312 } 313 } 314 return (T) value; 315 } 316 317 /* 318 * ----- LifeCycle ----- 319 */ 320 321 @Override 322 public String getLifeCyclePolicy() { 323 return (String) getPropertyValue(Model.MISC_LIFECYCLE_POLICY_PROP); 324 } 325 326 @Override 327 public void setLifeCyclePolicy(String policy) { 328 setPropertyValue(Model.MISC_LIFECYCLE_POLICY_PROP, policy); 329 BlobManager blobManager = Framework.getService(BlobManager.class); 330 blobManager.notifyChanges(this, Collections.singleton(Model.MISC_LIFECYCLE_POLICY_PROP)); 331 } 332 333 @Override 334 public String getLifeCycleState() { 335 return (String) getPropertyValue(Model.MISC_LIFECYCLE_STATE_PROP); 336 } 337 338 @Override 339 public void setCurrentLifeCycleState(String state) { 340 setPropertyValue(Model.MISC_LIFECYCLE_STATE_PROP, state); 341 BlobManager blobManager = Framework.getService(BlobManager.class); 342 blobManager.notifyChanges(this, Collections.singleton(Model.MISC_LIFECYCLE_STATE_PROP)); 343 } 344 345 @Override 346 public void followTransition(String transition) throws LifeCycleException { 347 LifeCycleService service = NXCore.getLifeCycleService(); 348 if (service == null) { 349 throw new NuxeoException("LifeCycleService not available"); 350 } 351 service.followTransition(this, transition); 352 } 353 354 @Override 355 public Collection<String> getAllowedStateTransitions() { 356 LifeCycleService service = NXCore.getLifeCycleService(); 357 if (service == null) { 358 throw new NuxeoException("LifeCycleService not available"); 359 } 360 LifeCycle lifeCycle = service.getLifeCycleFor(this); 361 if (lifeCycle == null) { 362 return Collections.emptyList(); 363 } 364 return lifeCycle.getAllowedStateTransitionsFrom(getLifeCycleState()); 365 } 366 367 /* 368 * ----- org.nuxeo.ecm.core.versioning.VersionableDocument ----- 369 */ 370 371 @Override 372 public boolean isVersion() { 373 return false; 374 } 375 376 @Override 377 public Document getBaseVersion() { 378 if (isCheckedOut()) { 379 return null; 380 } 381 Serializable id = (Serializable) getPropertyValue(Model.MAIN_BASE_VERSION_PROP); 382 if (id == null) { 383 // shouldn't happen 384 return null; 385 } 386 return session.getDocumentById(id); 387 } 388 389 @Override 390 public String getVersionSeriesId() { 391 return getUUID(); 392 } 393 394 @Override 395 public Document getSourceDocument() { 396 return this; 397 } 398 399 @Override 400 public Document checkIn(String label, String checkinComment) { 401 Document version = session.checkIn(getNode(), label, checkinComment); 402 Framework.getService(BlobManager.class).freezeVersion(version); 403 return version; 404 } 405 406 @Override 407 public void checkOut() { 408 session.checkOut(getNode()); 409 } 410 411 @Override 412 public boolean isCheckedOut() { 413 return !Boolean.TRUE.equals(getPropertyValue(Model.MAIN_CHECKED_IN_PROP)); 414 } 415 416 @Override 417 public boolean isMajorVersion() { 418 return false; 419 } 420 421 @Override 422 public boolean isLatestVersion() { 423 return false; 424 } 425 426 @Override 427 public boolean isLatestMajorVersion() { 428 return false; 429 } 430 431 @Override 432 public boolean isVersionSeriesCheckedOut() { 433 return isCheckedOut(); 434 } 435 436 @Override 437 public String getVersionLabel() { 438 return (String) getPropertyValue(Model.VERSION_LABEL_PROP); 439 } 440 441 @Override 442 public String getCheckinComment() { 443 return (String) getPropertyValue(Model.VERSION_DESCRIPTION_PROP); 444 } 445 446 @Override 447 public Document getWorkingCopy() { 448 return this; 449 } 450 451 @Override 452 public Calendar getVersionCreationDate() { 453 return (Calendar) getPropertyValue(Model.VERSION_CREATED_PROP); 454 } 455 456 @Override 457 public void restore(Document version) { 458 if (!version.isVersion()) { 459 throw new NuxeoException("Cannot restore a non-version: " + version); 460 } 461 session.restore(getNode(), ((SQLDocument) version).getNode()); 462 } 463 464 @Override 465 public List<String> getVersionsIds() { 466 String versionSeriesId = getVersionSeriesId(); 467 Collection<Document> versions = session.getVersions(versionSeriesId); 468 List<String> ids = new ArrayList<String>(versions.size()); 469 for (Document version : versions) { 470 ids.add(version.getUUID()); 471 } 472 return ids; 473 } 474 475 @Override 476 public Document getVersion(String label) { 477 String versionSeriesId = getVersionSeriesId(); 478 return session.getVersionByLabel(versionSeriesId, label); 479 } 480 481 @Override 482 public List<Document> getVersions() { 483 String versionSeriesId = getVersionSeriesId(); 484 return session.getVersions(versionSeriesId); 485 } 486 487 @Override 488 public Document getLastVersion() { 489 String versionSeriesId = getVersionSeriesId(); 490 return session.getLastVersion(versionSeriesId); 491 } 492 493 @Override 494 public Document getChild(String name) { 495 return session.getChild(getNode(), name); 496 } 497 498 @Override 499 public List<Document> getChildren() { 500 if (!isFolder()) { 501 return Collections.emptyList(); 502 } 503 return session.getChildren(getNode()); // newly allocated 504 } 505 506 @Override 507 public List<String> getChildrenIds() { 508 if (!isFolder()) { 509 return Collections.emptyList(); 510 } 511 // not optimized as this method doesn't seem to be used 512 List<Document> children = session.getChildren(getNode()); 513 List<String> ids = new ArrayList<String>(children.size()); 514 for (Document child : children) { 515 ids.add(child.getUUID()); 516 } 517 return ids; 518 } 519 520 @Override 521 public boolean hasChild(String name) { 522 if (!isFolder()) { 523 return false; 524 } 525 return session.hasChild(getNode(), name); 526 } 527 528 @Override 529 public boolean hasChildren() { 530 if (!isFolder()) { 531 return false; 532 } 533 return session.hasChildren(getNode()); 534 } 535 536 @Override 537 public Document addChild(String name, String typeName) { 538 if (!isFolder()) { 539 throw new IllegalArgumentException("Not a folder"); 540 } 541 return session.addChild(getNode(), name, null, typeName); 542 } 543 544 @Override 545 public void orderBefore(String src, String dest) { 546 SQLDocument srcDoc = (SQLDocument) getChild(src); 547 if (srcDoc == null) { 548 throw new DocumentNotFoundException("Document " + this + " has no child: " + src); 549 } 550 SQLDocument destDoc; 551 if (dest == null) { 552 destDoc = null; 553 } else { 554 destDoc = (SQLDocument) getChild(dest); 555 if (destDoc == null) { 556 throw new DocumentNotFoundException("Document " + this + " has no child: " + dest); 557 } 558 } 559 session.orderBefore(getNode(), srcDoc.getNode(), destDoc == null ? null : destDoc.getNode()); 560 } 561 562 @Override 563 public Set<String> getAllFacets() { 564 return getNode().getAllMixinTypes(); 565 } 566 567 @Override 568 public String[] getFacets() { 569 return getNode().getMixinTypes(); 570 } 571 572 @Override 573 public boolean hasFacet(String facet) { 574 return getNode().hasMixinType(facet); 575 } 576 577 @Override 578 public boolean addFacet(String facet) { 579 return session.addMixinType(getNode(), facet); 580 } 581 582 @Override 583 public boolean removeFacet(String facet) { 584 return session.removeMixinType(getNode(), facet); 585 } 586 587 /* 588 * ----- PropertyContainer inherited from SQLComplexProperty ----- 589 */ 590 591 /* 592 * ----- toString/equals/hashcode ----- 593 */ 594 595 @Override 596 public String toString() { 597 return getClass().getSimpleName() + '(' + getName() + ',' + getUUID() + ')'; 598 } 599 600 @Override 601 public boolean equals(Object other) { 602 if (other == this) { 603 return true; 604 } 605 if (other == null) { 606 return false; 607 } 608 if (other.getClass() == this.getClass()) { 609 return equals((SQLDocumentLive) other); 610 } 611 return false; 612 } 613 614 private boolean equals(SQLDocumentLive other) { 615 return getNode().equals(other.getNode()); 616 } 617 618 @Override 619 public int hashCode() { 620 return getNode().hashCode(); 621 } 622 623 @Override 624 public Document getTargetDocument() { 625 return null; 626 } 627 628 @Override 629 public void setTargetDocument(Document target) { 630 throw new NuxeoException("Not a proxy"); 631 } 632 633 @Override 634 protected Lock getDocumentLock() { 635 // lock manager can get the lock even on a recently created and unsaved document 636 throw new UnsupportedOperationException(); 637 } 638 639 @Override 640 protected Lock setDocumentLock(Lock lock) { 641 // lock manager can set the lock even on a recently created and unsaved document 642 throw new UnsupportedOperationException(); 643 } 644 645 @Override 646 protected Lock removeDocumentLock(String owner) { 647 // lock manager can remove the lock even on a recently created and unsaved document 648 throw new UnsupportedOperationException(); 649 } 650 651}