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 * Bogdan Stefanescu 011 * Florent Guillaume 012 */ 013package org.nuxeo.ecm.core.api; 014 015import java.io.Serializable; 016import java.util.Arrays; 017import java.util.HashSet; 018import java.util.LinkedList; 019import java.util.List; 020import java.util.Map; 021import java.util.Map.Entry; 022import java.util.Set; 023 024import org.apache.commons.logging.Log; 025import org.apache.commons.logging.LogFactory; 026import org.nuxeo.common.utils.Path; 027import org.nuxeo.ecm.core.api.DocumentModel.DocumentModelRefresh; 028import org.nuxeo.ecm.core.api.impl.DataModelImpl; 029import org.nuxeo.ecm.core.api.impl.DocumentModelImpl; 030import org.nuxeo.ecm.core.api.model.DocumentPart; 031import org.nuxeo.ecm.core.api.model.impl.DocumentPartImpl; 032import org.nuxeo.ecm.core.model.Document; 033import org.nuxeo.ecm.core.model.Document.WriteContext; 034import org.nuxeo.ecm.core.schema.DocumentType; 035import org.nuxeo.ecm.core.schema.FacetNames; 036import org.nuxeo.ecm.core.schema.Prefetch; 037import org.nuxeo.ecm.core.schema.PrefetchInfo; 038import org.nuxeo.ecm.core.schema.SchemaManager; 039import org.nuxeo.ecm.core.schema.TypeProvider; 040import org.nuxeo.ecm.core.schema.types.Field; 041import org.nuxeo.ecm.core.schema.types.ListType; 042import org.nuxeo.ecm.core.schema.types.Schema; 043import org.nuxeo.ecm.core.schema.types.Type; 044import org.nuxeo.runtime.api.Framework; 045 046/** 047 * Bridge between a {@link DocumentModel} and a {@link Document} for creation / update. 048 */ 049public class DocumentModelFactory { 050 051 private static final Log log = LogFactory.getLog(DocumentModelFactory.class); 052 053 // Utility class. 054 private DocumentModelFactory() { 055 } 056 057 /** 058 * Creates a document model for an existing document. 059 * 060 * @param doc the document 061 * @param sid the session id for this document 062 * @param schemas the schemas to prefetch (deprecated), or {@code null} 063 * @return the new document model 064 */ 065 public static DocumentModelImpl createDocumentModel(Document doc, String sid, String[] schemas) { 066 067 DocumentType type = doc.getType(); 068 if (type == null) { 069 throw new NuxeoException("Type not found for doc " + doc); 070 } 071 072 DocumentRef docRef = new IdRef(doc.getUUID()); 073 Document parent = doc.getParent(); 074 DocumentRef parentRef = parent == null ? null : new IdRef(parent.getUUID()); 075 076 // Compute document source id if exists 077 Document sourceDoc = doc.getSourceDocument(); 078 String sourceId = sourceDoc == null ? null : sourceDoc.getUUID(); 079 080 // Immutable flag 081 boolean immutable = doc.isVersion() || (doc.isProxy() && sourceDoc.isVersion()); 082 083 // Instance facets 084 Set<String> facets = new HashSet<String>(Arrays.asList(doc.getFacets())); 085 if (immutable) { 086 facets.add(FacetNames.IMMUTABLE); 087 } 088 089 // Compute repository name. 090 String repositoryName = doc.getRepositoryName(); 091 092 // versions being imported before their live doc don't have a path 093 String p = doc.getPath(); 094 Path path = p == null ? null : new Path(p); 095 096 // create the document model 097 // lock is unused 098 DocumentModelImpl docModel = new DocumentModelImpl(sid, type.getName(), doc.getUUID(), path, docRef, parentRef, 099 null, facets, sourceId, repositoryName, doc.isProxy()); 100 101 docModel.setPosInternal(doc.getPos()); 102 103 if (doc.isVersion()) { 104 docModel.setIsVersion(true); 105 } 106 if (immutable) { 107 docModel.setIsImmutable(true); 108 } 109 110 // populate prefetch 111 PrefetchInfo prefetchInfo = type.getPrefetchInfo(); 112 Prefetch prefetch; 113 String[] prefetchSchemas; 114 if (prefetchInfo != null) { 115 Set<String> docSchemas = new HashSet<String>(Arrays.asList(docModel.getSchemas())); 116 prefetch = getPrefetch(doc, prefetchInfo, docSchemas); 117 prefetchSchemas = prefetchInfo.getSchemas(); 118 } else { 119 prefetch = null; 120 prefetchSchemas = null; 121 } 122 123 // populate datamodels 124 List<String> loadSchemas = new LinkedList<String>(); 125 if (schemas == null) { 126 schemas = prefetchSchemas; 127 } 128 if (schemas != null) { 129 Set<String> validSchemas = new HashSet<String>(Arrays.asList(docModel.getSchemas())); 130 for (String schemaName : schemas) { 131 if (validSchemas.contains(schemaName)) { 132 loadSchemas.add(schemaName); 133 } 134 } 135 } 136 SchemaManager schemaManager = Framework.getLocalService(SchemaManager.class); 137 for (String schemaName : loadSchemas) { 138 Schema schema = schemaManager.getSchema(schemaName); 139 docModel.addDataModel(createDataModel(doc, schema)); 140 } 141 142 if (prefetch != null) { 143 // ignore prefetches already loaded as datamodels 144 for (String schemaName : loadSchemas) { 145 prefetch.clearPrefetch(schemaName); 146 } 147 // set prefetch 148 docModel.setPrefetch(prefetch); 149 } 150 151 // prefetch lifecycle state 152 try { 153 String lifeCycleState = doc.getLifeCycleState(); 154 docModel.prefetchCurrentLifecycleState(lifeCycleState); 155 String lifeCyclePolicy = doc.getLifeCyclePolicy(); 156 docModel.prefetchLifeCyclePolicy(lifeCyclePolicy); 157 } catch (LifeCycleException e) { 158 log.debug("Cannot prefetch lifecycle for doc: " + doc.getName() + ". Error: " + e.getMessage()); 159 } 160 161 return docModel; 162 } 163 164 /** 165 * Returns a document model computed from its type, querying the {@link SchemaManager} service. 166 * <p> 167 * The created document model is not linked to any core session. 168 * 169 * @since 5.4.2 170 */ 171 public static DocumentModelImpl createDocumentModel(String docType) { 172 SchemaManager schemaManager = Framework.getLocalService(SchemaManager.class); 173 DocumentType type = schemaManager.getDocumentType(docType); 174 return createDocumentModel(null, type); 175 } 176 177 /** 178 * Creates a document model for a new document. 179 * <p> 180 * Initializes the proper data models according to the type info. 181 * 182 * @param sessionId the CoreSession id 183 * @param docType the document type 184 * @return the document model 185 */ 186 public static DocumentModelImpl createDocumentModel(String sessionId, DocumentType docType) { 187 DocumentModelImpl docModel = new DocumentModelImpl(sessionId, docType.getName(), null, null, null, null, null, 188 null, null, null, null); 189 for (Schema schema : docType.getSchemas()) { 190 docModel.addDataModel(createDataModel(null, schema)); 191 } 192 return docModel; 193 } 194 195 /** 196 * Creates a data model from a document and a schema. If the document is null, just creates empty data models. 197 */ 198 public static DataModel createDataModel(Document doc, Schema schema) { 199 DocumentPart part = new DocumentPartImpl(schema); 200 if (doc != null) { 201 doc.readDocumentPart(part); 202 } 203 return new DataModelImpl(part); 204 } 205 206 /** 207 * Writes a document model to a document. Returns the re-read document model. 208 */ 209 public static DocumentModel writeDocumentModel(DocumentModel docModel, Document doc) { 210 if (!(docModel instanceof DocumentModelImpl)) { 211 throw new NuxeoException("Must be a DocumentModelImpl: " + docModel); 212 } 213 214 boolean changed = false; 215 216 // facets added/removed 217 Set<String> instanceFacets = ((DocumentModelImpl) docModel).instanceFacets; 218 Set<String> instanceFacetsOrig = ((DocumentModelImpl) docModel).instanceFacetsOrig; 219 Set<String> addedFacets = new HashSet<String>(instanceFacets); 220 addedFacets.removeAll(instanceFacetsOrig); 221 for (String facet : addedFacets) { 222 changed = doc.addFacet(facet) || changed; 223 } 224 Set<String> removedFacets = new HashSet<String>(instanceFacetsOrig); 225 removedFacets.removeAll(instanceFacets); 226 for (String facet : removedFacets) { 227 changed = doc.removeFacet(facet) || changed; 228 } 229 230 // write data models 231 // check only the loaded ones to find the dirty ones 232 WriteContext writeContext = doc.getWriteContext(); 233 for (DataModel dm : docModel.getDataModelsCollection()) { // only loaded 234 if (dm.isDirty()) { 235 DocumentPart part = ((DataModelImpl) dm).getDocumentPart(); 236 changed = doc.writeDocumentPart(part, writeContext) || changed; 237 } 238 } 239 // write the blobs last, so that blob providers have access to the new doc state 240 writeContext.flush(doc); 241 242 if (!changed) { 243 return docModel; 244 } 245 246 // TODO: here we can optimize document part doesn't need to be read 247 DocumentModel newModel = createDocumentModel(doc, docModel.getSessionId(), null); 248 newModel.copyContextData(docModel); 249 return newModel; 250 } 251 252 /** 253 * Gets what's to refresh in a model (except for the ACPs, which need the session). 254 */ 255 public static DocumentModelRefresh refreshDocumentModel(Document doc, int flags, String[] schemas) 256 throws LifeCycleException { 257 DocumentModelRefresh refresh = new DocumentModelRefresh(); 258 259 refresh.instanceFacets = new HashSet<String>(Arrays.asList(doc.getFacets())); 260 Set<String> docSchemas = DocumentModelImpl.computeSchemas(doc.getType(), refresh.instanceFacets, doc.isProxy()); 261 262 if ((flags & DocumentModel.REFRESH_PREFETCH) != 0) { 263 PrefetchInfo prefetchInfo = doc.getType().getPrefetchInfo(); 264 if (prefetchInfo != null) { 265 refresh.prefetch = getPrefetch(doc, prefetchInfo, docSchemas); 266 } 267 } 268 269 if ((flags & DocumentModel.REFRESH_STATE) != 0) { 270 refresh.lifeCycleState = doc.getLifeCycleState(); 271 refresh.lifeCyclePolicy = doc.getLifeCyclePolicy(); 272 refresh.isCheckedOut = doc.isCheckedOut(); 273 refresh.isLatestVersion = doc.isLatestVersion(); 274 refresh.isMajorVersion = doc.isMajorVersion(); 275 refresh.isLatestMajorVersion = doc.isLatestMajorVersion(); 276 refresh.isVersionSeriesCheckedOut = doc.isVersionSeriesCheckedOut(); 277 refresh.versionSeriesId = doc.getVersionSeriesId(); 278 refresh.checkinComment = doc.getCheckinComment(); 279 } 280 281 if ((flags & DocumentModel.REFRESH_CONTENT) != 0) { 282 if (schemas == null) { 283 schemas = docSchemas.toArray(new String[0]); 284 } 285 TypeProvider typeProvider = Framework.getLocalService(SchemaManager.class); 286 DocumentPart[] parts = new DocumentPart[schemas.length]; 287 for (int i = 0; i < schemas.length; i++) { 288 DocumentPart part = new DocumentPartImpl(typeProvider.getSchema(schemas[i])); 289 doc.readDocumentPart(part); 290 parts[i] = part; 291 } 292 refresh.documentParts = parts; 293 } 294 295 return refresh; 296 } 297 298 /** 299 * Prefetches from a document. 300 */ 301 protected static Prefetch getPrefetch(Document doc, PrefetchInfo prefetchInfo, Set<String> docSchemas) { 302 SchemaManager schemaManager = Framework.getLocalService(SchemaManager.class); 303 304 // individual fields 305 Set<String> xpaths = new HashSet<String>(); 306 String[] prefetchFields = prefetchInfo.getFields(); 307 if (prefetchFields != null) { 308 xpaths.addAll(Arrays.asList(prefetchFields)); 309 } 310 311 // whole schemas (but NOT their complex properties) 312 String[] prefetchSchemas = prefetchInfo.getSchemas(); 313 if (prefetchSchemas != null) { 314 for (String schemaName : prefetchSchemas) { 315 if (docSchemas.contains(schemaName)) { 316 Schema schema = schemaManager.getSchema(schemaName); 317 if (schema != null) { 318 for (Field field : schema.getFields()) { 319 if (isScalarField(field)) { 320 xpaths.add(field.getName().getPrefixedName()); 321 } 322 } 323 } 324 } 325 } 326 } 327 328 // do the prefetch 329 Prefetch prefetch = new Prefetch(); 330 for (String schemaName : docSchemas) { 331 Schema schema = schemaManager.getSchema(schemaName); 332 // find xpaths for this schema 333 Set<String> schemaXpaths = new HashSet<String>(); 334 for (String xpath : xpaths) { 335 String sn = DocumentModelImpl.getXPathSchemaName(xpath, docSchemas, null); 336 if (schemaName.equals(sn)) { 337 schemaXpaths.add(xpath); 338 } 339 } 340 if (schemaXpaths.isEmpty()) { 341 continue; 342 } 343 Map<String, Serializable> map = doc.readPrefetch(schema, schemaXpaths); 344 for (Entry<String, Serializable> en : map.entrySet()) { 345 String xpath = en.getKey(); 346 Serializable value = en.getValue(); 347 String[] returnName = new String[1]; 348 String sn = DocumentModelImpl.getXPathSchemaName(xpath, docSchemas, returnName); 349 String name = returnName[0]; 350 prefetch.put(xpath, sn, name, value); 351 } 352 } 353 354 return prefetch; 355 } 356 357 /** 358 * Checks if a field is a primitive type or array. 359 */ 360 protected static boolean isScalarField(Field field) { 361 Type type = field.getType(); 362 if (type.isComplexType()) { 363 // complex type 364 return false; 365 } 366 if (!type.isListType()) { 367 // primitive type 368 return true; 369 } 370 // array or complex list? 371 return ((ListType) type).getFieldType().isSimpleType(); 372 } 373 374 /** 375 * Create an empty documentmodel for a given type with its id already setted. This can be useful when trying to 376 * attach a documentmodel that has been serialized and modified. 377 * 378 * @param type 379 * @param id 380 * @return 381 * @since 5.7.2 382 */ 383 public static DocumentModel createDocumentModel(String type, String id) { 384 SchemaManager sm = Framework.getLocalService(SchemaManager.class); 385 DocumentType docType = sm.getDocumentType(type); 386 DocumentModel doc = new DocumentModelImpl(null, docType.getName(), id, null, null, new IdRef(id), null, null, 387 null, null, null); 388 for (Schema schema : docType.getSchemas()) { 389 ((DocumentModelImpl) doc).addDataModel(createDataModel(null, schema)); 390 } 391 return doc; 392 } 393 394}