001/* 002 * (C) Copyright 2006-2020 Nuxeo (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 * Thomas Roger 018 * Dragos Mihalache 019 * Florent Guillaume 020 */ 021package org.nuxeo.ecm.core.api.impl; 022 023import static org.nuxeo.ecm.core.schema.types.ComplexTypeImpl.canonicalXPath; 024 025import java.io.Serializable; 026import java.util.Arrays; 027import java.util.Collection; 028import java.util.Collections; 029import java.util.HashMap; 030import java.util.HashSet; 031import java.util.List; 032import java.util.Map; 033import java.util.Set; 034import java.util.stream.Collectors; 035import java.util.stream.Stream; 036 037import org.nuxeo.common.utils.Path; 038import org.nuxeo.ecm.core.api.CoreSession; 039import org.nuxeo.ecm.core.api.DataModel; 040import org.nuxeo.ecm.core.api.DocumentModel; 041import org.nuxeo.ecm.core.api.DocumentRef; 042import org.nuxeo.ecm.core.api.LifeCycleConstants; 043import org.nuxeo.ecm.core.api.Lock; 044import org.nuxeo.ecm.core.api.NuxeoPrincipal; 045import org.nuxeo.ecm.core.api.PathRef; 046import org.nuxeo.ecm.core.api.PropertyException; 047import org.nuxeo.ecm.core.api.VersioningOption; 048import org.nuxeo.ecm.core.api.model.DocumentPart; 049import org.nuxeo.ecm.core.api.model.Property; 050import org.nuxeo.ecm.core.api.model.PropertyNotFoundException; 051import org.nuxeo.ecm.core.api.model.PropertyVisitor; 052import org.nuxeo.ecm.core.api.model.impl.DocumentPartImpl; 053import org.nuxeo.ecm.core.api.model.resolver.DocumentPropertyObjectResolverImpl; 054import org.nuxeo.ecm.core.api.model.resolver.PropertyObjectResolver; 055import org.nuxeo.ecm.core.api.security.ACP; 056import org.nuxeo.ecm.core.schema.DocumentType; 057import org.nuxeo.ecm.core.schema.SchemaManager; 058import org.nuxeo.ecm.core.schema.types.CompositeType; 059import org.nuxeo.ecm.core.schema.types.Schema; 060import org.nuxeo.runtime.api.Framework; 061 062/** 063 * A DocumentModel that can have any schema and is not made persistent by itself. A mockup to keep arbitrary schema 064 * data. 065 */ 066public class SimpleDocumentModel implements DocumentModel { 067 068 private static final long serialVersionUID = 1L; 069 070 protected final Map<String, DataModel> dataModels = new HashMap<>(); 071 072 protected final Set<String> schemas = new HashSet<>(); 073 074 protected final Set<String> facets = new HashSet<>(); 075 076 protected final Map<String, Serializable> contextData = new HashMap<>(); 077 078 protected final boolean anySchema; 079 080 protected Path path; 081 082 protected String type; 083 084 /** 085 * @deprecated since 11.1. Use {@link #empty()} instead. 086 */ 087 @Deprecated(since = "11.1") 088 public SimpleDocumentModel() { 089 anySchema = true; 090 } 091 092 /** 093 * Returns a new empty {@link SimpleDocumentModel} instance. 094 * 095 * @since 11.1 096 */ 097 public static SimpleDocumentModel empty() { 098 return new SimpleDocumentModel(); 099 } 100 101 /** 102 * @since 11.1 103 */ 104 protected SimpleDocumentModel(DocumentType documentType) { 105 anySchema = false; 106 type = documentType.getName(); 107 initSchemas(List.of(documentType.getSchemaNames())); 108 } 109 110 /** 111 * Returns a {@link SimpleDocumentModel} instance initialized with the given {@code type} and its related schemas. 112 * 113 * @since 11.1 114 */ 115 public static SimpleDocumentModel ofType(String type) { 116 SchemaManager service = Framework.getService(SchemaManager.class); 117 DocumentType dType = service.getDocumentType(type); 118 return new SimpleDocumentModel(dType); 119 } 120 121 /** 122 * @deprecated since 11.1. Use {@link #ofSchemas(List)} instead. 123 */ 124 @Deprecated(since = "11.1") 125 public SimpleDocumentModel(List<String> schemas) { 126 anySchema = false; 127 initSchemas(schemas); 128 } 129 130 /** 131 * Returns a {@link SimpleDocumentModel} instance initialized with the given {@code schemas}. 132 * 133 * @since 11.1 134 */ 135 public static SimpleDocumentModel ofSchemas(List<String> schemas) { 136 return new SimpleDocumentModel(schemas); 137 } 138 139 /** 140 * @deprecated since 11.1. Use {@link #ofSchemas(String, String...)} instead. 141 */ 142 @Deprecated(since = "11.1") 143 public SimpleDocumentModel(String... schemas) { 144 this(List.of(schemas)); 145 } 146 147 /** 148 * Returns a {@link SimpleDocumentModel} instance initialized with the given {@code schema} and optional 149 * {@code schemas}. 150 * 151 * @since 11.1 152 */ 153 public static SimpleDocumentModel ofSchemas(String schema, String... schemas) { 154 return ofSchemas(Stream.concat(Stream.of(schema), Stream.of(schemas)).collect(Collectors.toList())); 155 } 156 157 protected final void initSchemas(List<String> schemas) { 158 SchemaManager schemaManager = Framework.getService(SchemaManager.class); 159 for (String schema : schemas) { 160 Schema s = schemaManager.getSchema(schema); 161 DocumentPart part = new DocumentPartImpl(s); 162 dataModels.put(schema, new DataModelImpl(part)); 163 this.schemas.add(schema); 164 } 165 } 166 167 protected DataModel getDataModelInternal(String schema) { 168 DataModel dm = dataModels.get(schema); 169 if (dm == null && anySchema) { 170 SchemaManager schemaManager = Framework.getService(SchemaManager.class); 171 Schema s = schemaManager.getSchema(schema); 172 DocumentPart part = new DocumentPartImpl(s); 173 dm = new DataModelImpl(part); 174 dataModels.put(schema, dm); 175 schemas.add(schema); 176 } 177 return dm; 178 } 179 180 @Override 181 public String[] getSchemas() { 182 Set<String> keys = dataModels.keySet(); 183 return keys.toArray(new String[0]); 184 } 185 186 @Override 187 @SuppressWarnings("deprecation") 188 public Object getProperty(String schemaName, String name) { 189 DataModel dm = getDataModelInternal(schemaName); 190 return dm != null ? dm.getData(name) : null; 191 } 192 193 @Override 194 @SuppressWarnings("deprecation") 195 public Property getPropertyObject(String schema, String name) { 196 DocumentPart part = getPart(schema); 197 return part == null ? null : part.get(name); 198 } 199 200 @Override 201 public void setProperty(String schemaName, String name, Object value) { 202 if (name.contains(":")) { 203 name = name.substring(name.indexOf(':')); 204 } 205 getDataModelInternal(schemaName).setData(name, value); 206 } 207 208 @Override 209 public Map<String, Object> getProperties(String schemaName) { 210 return getDataModelInternal(schemaName).getMap(); 211 } 212 213 @Override 214 @SuppressWarnings("deprecation") 215 public void setProperties(String schemaName, Map<String, Object> data) { 216 DataModel dm = getDataModelInternal(schemaName); 217 dm.setMap(data); 218 // force dirty for updated properties 219 for (String field : data.keySet()) { 220 dm.setDirty(field); 221 } 222 } 223 224 @Override 225 public Map<String, Serializable> getContextData() { 226 return contextData; 227 } 228 229 @Override 230 public Serializable getContextData(String key) { 231 return contextData.get(key); 232 } 233 234 @Override 235 public void putContextData(String key, Serializable value) { 236 contextData.put(key, value); 237 } 238 239 @Override 240 public void copyContextData(DocumentModel otherDocument) { 241 contextData.putAll(otherDocument.getContextData()); 242 } 243 244 @Override 245 @SuppressWarnings("deprecation") 246 public Property getProperty(String xpath) throws PropertyException { 247 if (xpath == null) { 248 throw new PropertyNotFoundException("null", "Invalid null xpath"); 249 } 250 String cxpath = canonicalXPath(xpath); 251 if (cxpath.isEmpty()) { 252 throw new PropertyNotFoundException(xpath, "Schema not specified"); 253 } 254 String schemaName = DocumentModelImpl.getXPathSchemaName(cxpath, schemas, null); 255 if (schemaName == null) { 256 if (cxpath.indexOf(':') != -1) { 257 throw new PropertyNotFoundException(xpath, "No such schema"); 258 } else { 259 throw new PropertyNotFoundException(xpath); 260 } 261 262 } 263 DocumentPart part = getPart(schemaName); 264 if (part == null) { 265 throw new PropertyNotFoundException(xpath); 266 } 267 // cut prefix 268 String partPath = cxpath.substring(cxpath.indexOf(':') + 1); 269 try { 270 Property property = part.resolvePath(partPath); 271 // force dirty for updated properties 272 property.setForceDirty(true); 273 return property; 274 } catch (PropertyNotFoundException e) { 275 throw new PropertyNotFoundException(xpath, e.getDetail()); 276 } 277 } 278 279 @Override 280 public Serializable getPropertyValue(String xpath) throws PropertyException { 281 return getProperty(xpath).getValue(); 282 } 283 284 @Override 285 public void setPropertyValue(String xpath, Serializable value) { 286 getProperty(xpath).setValue(value); 287 } 288 289 @Override 290 public DocumentType getDocumentType() { 291 throw new UnsupportedOperationException(); 292 } 293 294 @Override 295 public String getSessionId() { 296 throw new UnsupportedOperationException(); 297 } 298 299 @Override 300 public NuxeoPrincipal getPrincipal() { 301 throw new UnsupportedOperationException(); 302 } 303 304 @Override 305 public CoreSession getCoreSession() { 306 throw new UnsupportedOperationException(); 307 } 308 309 @Override 310 public void detach(boolean loadAll) { 311 } 312 313 @Override 314 public void attach(CoreSession coreSession) { 315 } 316 317 @Override 318 public boolean isAttached() { 319 return false; 320 } 321 322 @Override 323 public DocumentRef getRef() { 324 throw new UnsupportedOperationException(); 325 } 326 327 @Override 328 public DocumentRef getParentRef() { 329 if (path == null) { 330 return null; 331 } 332 if (!path.isAbsolute()) { 333 return null; 334 } 335 return new PathRef(path.removeLastSegments(1).toString()); 336 } 337 338 @Override 339 public String getId() { 340 throw new UnsupportedOperationException(); 341 } 342 343 @Override 344 public String getName() { 345 return path == null ? null : path.lastSegment(); 346 } 347 348 @Override 349 public Long getPos() { 350 return null; 351 } 352 353 @Override 354 public String getPathAsString() { 355 return path == null ? null : path.toString(); 356 } 357 358 @Override 359 public Path getPath() { 360 return path; 361 } 362 363 @Override 364 public String getTitle() { 365 throw new UnsupportedOperationException(); 366 } 367 368 @Override 369 public String getType() { 370 return type; 371 } 372 373 /** 374 * @deprecated since 11.1. Use {@link #ofType(String)}. 375 */ 376 @Deprecated(since = "11.1") 377 public void setType(String type) { 378 this.type = type; 379 } 380 381 @Override 382 public Set<String> getFacets() { 383 throw new UnsupportedOperationException(); 384 } 385 386 @Override 387 @Deprecated 388 public Collection<DataModel> getDataModelsCollection() { 389 throw new UnsupportedOperationException(); 390 } 391 392 @Override 393 @Deprecated 394 public Map<String, DataModel> getDataModels() { 395 return dataModels; 396 } 397 398 @Override 399 @Deprecated 400 public DataModel getDataModel(String schema) { 401 return getDataModelInternal(schema); 402 } 403 404 @Override 405 public void setPathInfo(String parentPath, String name) { 406 path = new Path(parentPath == null ? name : parentPath + '/' + name); 407 } 408 409 @Override 410 public boolean isLocked() { 411 throw new UnsupportedOperationException(); 412 } 413 414 @Override 415 public Lock setLock() { 416 throw new UnsupportedOperationException(); 417 } 418 419 @Override 420 public Lock getLockInfo() { 421 throw new UnsupportedOperationException(); 422 } 423 424 @Override 425 public Lock removeLock() { 426 throw new UnsupportedOperationException(); 427 } 428 429 @Override 430 public ACP getACP() { 431 throw new UnsupportedOperationException(); 432 } 433 434 @Override 435 public void setACP(ACP acp, boolean overwrite) { 436 throw new UnsupportedOperationException(); 437 } 438 439 @Override 440 public boolean hasSchema(String schema) { 441 throw new UnsupportedOperationException(); 442 } 443 444 @Override 445 public boolean hasFacet(String facet) { 446 return facets.contains(facet); 447 } 448 449 @Override 450 public boolean addFacet(String facet) { 451 if (facet == null) { 452 throw new IllegalArgumentException("Null facet"); 453 } 454 if (facets.contains(facet)) { 455 return false; 456 } 457 SchemaManager schemaManager = Framework.getService(SchemaManager.class); 458 CompositeType facetType = schemaManager.getFacet(facet); 459 if (facetType == null) { 460 throw new IllegalArgumentException("No such facet: " + facet); 461 } 462 // add it 463 facets.add(facet); 464 schemas.addAll(Arrays.asList(facetType.getSchemaNames())); 465 466 for (Schema schema : facetType.getSchemas()) { 467 DocumentPart part = new DocumentPartImpl(schema); 468 dataModels.put(schema.getName(), new DataModelImpl(part)); 469 } 470 471 return true; 472 } 473 474 @Override 475 public boolean removeFacet(String facet) { 476 // not implemented for now because logic is complex as we need to know initial type/schema 477 throw new UnsupportedOperationException(); 478 } 479 480 @Override 481 public boolean isTrashed() { 482 return LifeCycleConstants.DELETED_STATE.equals(getCurrentLifeCycleState()); 483 } 484 485 @Override 486 public boolean isFolder() { 487 throw new UnsupportedOperationException(); 488 } 489 490 @Override 491 public boolean isVersionable() { 492 throw new UnsupportedOperationException(); 493 } 494 495 @Override 496 public boolean isDownloadable() { 497 throw new UnsupportedOperationException(); 498 } 499 500 @Override 501 public boolean isVersion() { 502 throw new UnsupportedOperationException(); 503 } 504 505 @Override 506 public boolean isProxy() { 507 throw new UnsupportedOperationException(); 508 } 509 510 @Override 511 public boolean isImmutable() { 512 throw new UnsupportedOperationException(); 513 } 514 515 @Override 516 public boolean isDirty() { 517 throw new UnsupportedOperationException(); 518 } 519 520 @Override 521 public void accept(PropertyVisitor visitor, Object arg) { 522 throw new UnsupportedOperationException(); 523 } 524 525 @Override 526 public <T> T getAdapter(Class<T> itf) { 527 throw new UnsupportedOperationException(); 528 } 529 530 @Override 531 public <T> T getAdapter(Class<T> itf, boolean refreshCache) { 532 throw new UnsupportedOperationException(); 533 } 534 535 @Override 536 public String getCurrentLifeCycleState() { 537 throw new UnsupportedOperationException(); 538 } 539 540 @Override 541 public String getLifeCyclePolicy() { 542 throw new UnsupportedOperationException(); 543 } 544 545 @Override 546 public boolean followTransition(String transition) { 547 throw new UnsupportedOperationException(); 548 } 549 550 @Override 551 public Collection<String> getAllowedStateTransitions() { 552 throw new UnsupportedOperationException(); 553 } 554 555 @Override 556 public void copyContent(DocumentModel sourceDoc) { 557 throw new UnsupportedOperationException(); 558 } 559 560 @Override 561 public String getRepositoryName() { 562 throw new UnsupportedOperationException(); 563 } 564 565 @Override 566 public String getCacheKey() { 567 throw new UnsupportedOperationException(); 568 } 569 570 @Override 571 public String getSourceId() { 572 throw new UnsupportedOperationException(); 573 } 574 575 @Override 576 public String getVersionLabel() { 577 throw new UnsupportedOperationException(); 578 } 579 580 @Override 581 public String getCheckinComment() { 582 throw new UnsupportedOperationException(); 583 } 584 585 @Override 586 public boolean isPrefetched(String xpath) { 587 return false; 588 } 589 590 @Override 591 public boolean isPrefetched(String schemaName, String name) { 592 return false; 593 } 594 595 @Override 596 public void prefetchCurrentLifecycleState(String lifecycle) { 597 throw new UnsupportedOperationException(); 598 } 599 600 @Override 601 public void prefetchLifeCyclePolicy(String lifeCyclePolicy) { 602 throw new UnsupportedOperationException(); 603 } 604 605 @Override 606 public boolean isLifeCycleLoaded() { 607 throw new UnsupportedOperationException(); 608 } 609 610 @Override 611 public <T extends Serializable> T getSystemProp(String systemProperty, Class<T> type) { 612 throw new UnsupportedOperationException(); 613 } 614 615 @Override 616 @Deprecated 617 public DocumentPart getPart(String schema) { 618 DataModel dm = getDataModel(schema); 619 if (dm != null) { 620 return ((DataModelImpl) dm).getDocumentPart(); 621 } 622 return null; 623 } 624 625 @Override 626 @Deprecated 627 public DocumentPart[] getParts() { 628 throw new UnsupportedOperationException(); 629 } 630 631 @Override 632 @SuppressWarnings("deprecation") 633 public Collection<Property> getPropertyObjects(String schema) { 634 DocumentPart part = getPart(schema); 635 return part == null ? Collections.emptyList() : part.getChildren(); 636 } 637 638 @Override 639 public void reset() { 640 throw new UnsupportedOperationException(); 641 } 642 643 @Override 644 public void refresh(int refreshFlags, String[] schemas) { 645 throw new UnsupportedOperationException(); 646 } 647 648 @Override 649 public void refresh() { 650 throw new UnsupportedOperationException(); 651 } 652 653 @Override 654 public DocumentModel clone() { 655 throw new UnsupportedOperationException(); 656 } 657 658 @Override 659 public boolean isCheckedOut() { 660 throw new UnsupportedOperationException(); 661 } 662 663 @Override 664 public void checkOut() { 665 throw new UnsupportedOperationException(); 666 } 667 668 @Override 669 public DocumentRef checkIn(VersioningOption option, String description) { 670 throw new UnsupportedOperationException(); 671 } 672 673 @Override 674 public String getVersionSeriesId() { 675 throw new UnsupportedOperationException(); 676 } 677 678 @Override 679 public boolean isLatestVersion() { 680 return false; 681 } 682 683 @Override 684 public boolean isMajorVersion() { 685 return false; 686 } 687 688 @Override 689 public boolean isLatestMajorVersion() { 690 return false; 691 } 692 693 @Override 694 public boolean isVersionSeriesCheckedOut() { 695 return true; 696 } 697 698 @Override 699 public String getChangeToken() { 700 return null; 701 } 702 703 @Override 704 public Map<String, String> getBinaryFulltext() { 705 return null; 706 } 707 708 @Override 709 public PropertyObjectResolver getObjectResolver(String xpath) { 710 return DocumentPropertyObjectResolverImpl.create(this, xpath); 711 } 712 713}