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