001/* 002 * (C) Copyright 2006-2018 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 * Bogdan Stefanescu 018 * Florent Guillaume 019 */ 020package org.nuxeo.ecm.core.api; 021 022import java.util.Arrays; 023import java.util.HashSet; 024import java.util.LinkedList; 025import java.util.List; 026import java.util.Set; 027 028import org.apache.commons.lang3.StringUtils; 029import org.apache.commons.logging.Log; 030import org.apache.commons.logging.LogFactory; 031import org.nuxeo.common.utils.Path; 032import org.nuxeo.ecm.core.api.DocumentModel.DocumentModelRefresh; 033import org.nuxeo.ecm.core.api.impl.DataModelImpl; 034import org.nuxeo.ecm.core.api.impl.DocumentModelImpl; 035import org.nuxeo.ecm.core.api.model.DocumentPart; 036import org.nuxeo.ecm.core.api.model.impl.DocumentPartImpl; 037import org.nuxeo.ecm.core.model.Document; 038import org.nuxeo.ecm.core.model.Document.WriteContext; 039import org.nuxeo.ecm.core.schema.DocumentType; 040import org.nuxeo.ecm.core.schema.FacetNames; 041import org.nuxeo.ecm.core.schema.PrefetchInfo; 042import org.nuxeo.ecm.core.schema.SchemaManager; 043import org.nuxeo.ecm.core.schema.TypeProvider; 044import org.nuxeo.ecm.core.schema.types.Schema; 045import org.nuxeo.runtime.api.Framework; 046 047/** 048 * Bridge between a {@link DocumentModel} and a {@link Document} for creation / update. 049 */ 050public class DocumentModelFactory { 051 052 private static final Log log = LogFactory.getLog(DocumentModelFactory.class); 053 054 // Utility class. 055 private DocumentModelFactory() { 056 } 057 058 /** 059 * Creates a document model for an existing document. 060 * 061 * @param doc the document 062 * @param sid the session id for this document 063 * @param schemas the schemas to prefetch (deprecated), or {@code null} 064 * @return the new document model 065 */ 066 public static DocumentModelImpl createDocumentModel(Document doc, String sid, String[] schemas) { 067 068 DocumentType type = doc.getType(); 069 if (type == null) { 070 throw new NuxeoException("Type not found for doc " + doc); 071 } 072 073 DocumentRef docRef = new IdRef(doc.getUUID()); 074 Document parent = doc.getParent(); 075 DocumentRef parentRef = parent == null ? null : new IdRef(parent.getUUID()); 076 077 // Compute document source id if exists 078 Document sourceDoc = doc.getSourceDocument(); 079 String sourceId = sourceDoc == null ? null : sourceDoc.getUUID(); 080 081 // Immutable flag 082 boolean immutable = doc.isVersion() || (doc.isProxy() && sourceDoc.isVersion()); // NOSONAR (proxy has source) 083 084 // Instance facets 085 Set<String> facets = new HashSet<>(Arrays.asList(doc.getFacets())); 086 if (immutable) { 087 facets.add(FacetNames.IMMUTABLE); 088 } 089 090 // Compute repository name. 091 String repositoryName = doc.getRepositoryName(); 092 093 // versions being imported before their live doc don't have a path 094 String p = doc.getPath(); 095 Path path = p == null ? null : new Path(p); 096 097 // create the document model 098 // lock is unused 099 DocumentModelImpl docModel = new DocumentModelImpl(sid, type.getName(), doc.getUUID(), path, docRef, parentRef, 100 null, facets, sourceId, repositoryName, doc.isProxy()); 101 102 docModel.setPosInternal(doc.getPos()); 103 104 if (doc.isVersion()) { 105 docModel.setIsVersion(true); 106 } 107 if (immutable) { 108 docModel.setIsImmutable(true); 109 } 110 111 // populate datamodels 112 List<String> loadSchemas = new LinkedList<>(); 113 if (schemas == null) { 114 PrefetchInfo prefetchInfo = type.getPrefetchInfo(); 115 if (prefetchInfo != null) { 116 schemas = prefetchInfo.getSchemas(); 117 } 118 } 119 if (schemas != null) { 120 Set<String> validSchemas = new HashSet<>(Arrays.asList(docModel.getSchemas())); 121 for (String schemaName : schemas) { 122 if (validSchemas.contains(schemaName)) { 123 loadSchemas.add(schemaName); 124 } 125 } 126 } 127 SchemaManager schemaManager = Framework.getService(SchemaManager.class); 128 for (String schemaName : loadSchemas) { 129 Schema schema = schemaManager.getSchema(schemaName); 130 docModel.addDataModel(createDataModel(doc, schema)); 131 } 132 133 // prefetch lifecycle state 134 try { 135 String lifeCycleState = doc.getLifeCycleState(); 136 docModel.prefetchCurrentLifecycleState(lifeCycleState); 137 String lifeCyclePolicy = doc.getLifeCyclePolicy(); 138 docModel.prefetchLifeCyclePolicy(lifeCyclePolicy); 139 } catch (LifeCycleException e) { 140 log.debug("Cannot prefetch lifecycle for doc: " + doc.getName() + ". Error: " + e.getMessage()); 141 } 142 143 return docModel; 144 } 145 146 /** 147 * Returns a document model computed from its type, querying the {@link SchemaManager} service. 148 * <p> 149 * The created document model is not linked to any core session. 150 * 151 * @since 5.4.2 152 */ 153 public static DocumentModelImpl createDocumentModel(String docType) { 154 SchemaManager schemaManager = Framework.getService(SchemaManager.class); 155 DocumentType type = schemaManager.getDocumentType(docType); 156 return createDocumentModel(null, type); 157 } 158 159 /** 160 * Creates a document model for a new document. 161 * <p> 162 * Initializes the proper data models according to the type info. 163 * 164 * @param sessionId the CoreSession id 165 * @param docType the document type 166 * @return the document model 167 */ 168 public static DocumentModelImpl createDocumentModel(String sessionId, DocumentType docType) { 169 DocumentModelImpl docModel = new DocumentModelImpl(sessionId, docType.getName(), null, null, null, null, null, 170 null, null, null, null); 171 for (Schema schema : docType.getSchemas()) { 172 docModel.addDataModel(createDataModel(null, schema)); 173 } 174 return docModel; 175 } 176 177 /** 178 * Creates a data model from a document and a schema. If the document is null, just creates empty data models. 179 */ 180 public static DataModel createDataModel(Document doc, Schema schema) { 181 DocumentPart part = new DocumentPartImpl(schema); 182 if (doc != null) { 183 doc.readDocumentPart(part); 184 } 185 return new DataModelImpl(part); 186 } 187 188 /** 189 * Writes a document model to a document. Returns the re-read document model. 190 */ 191 public static DocumentModel writeDocumentModel(DocumentModel docModel, Document doc) { 192 if (!(docModel instanceof DocumentModelImpl)) { 193 throw new NuxeoException("Must be a DocumentModelImpl: " + docModel); 194 } 195 196 boolean changed = false; 197 198 // change token 199 String changeToken = (String) docModel.getContextData(CoreSession.CHANGE_TOKEN); 200 boolean userChange = StringUtils.isNotEmpty(changeToken); 201 if (!doc.validateUserVisibleChangeToken(changeToken)) { 202 throw new ConcurrentUpdateException(doc.getUUID()); 203 } 204 userChange = userChange || Boolean.TRUE.equals(docModel.getContextData(CoreSession.USER_CHANGE)); 205 docModel.putContextData(CoreSession.USER_CHANGE, null); 206 if (userChange) { 207 doc.markUserChange(); 208 } 209 210 // facets added/removed 211 Set<String> instanceFacets = ((DocumentModelImpl) docModel).instanceFacets; 212 Set<String> instanceFacetsOrig = ((DocumentModelImpl) docModel).instanceFacetsOrig; 213 Set<String> addedFacets = new HashSet<>(instanceFacets); 214 addedFacets.removeAll(instanceFacetsOrig); 215 addedFacets.remove(FacetNames.IMMUTABLE); 216 for (String facet : addedFacets) { 217 changed = doc.addFacet(facet) || changed; 218 } 219 Set<String> removedFacets = new HashSet<>(instanceFacetsOrig); 220 removedFacets.removeAll(instanceFacets); 221 for (String facet : removedFacets) { 222 changed = doc.removeFacet(facet) || changed; 223 } 224 225 // write data models 226 // check only the loaded ones to find the dirty ones 227 WriteContext writeContext = doc.getWriteContext(); 228 for (DataModel dm : docModel.getDataModelsCollection()) { // only loaded 229 if (dm.isDirty()) { 230 DocumentPart part = ((DataModelImpl) dm).getDocumentPart(); 231 changed = doc.writeDocumentPart(part, writeContext) || changed; 232 } 233 } 234 // write the blobs last, so that blob providers have access to the new doc state 235 writeContext.flush(doc); 236 237 if (!changed) { 238 return docModel; 239 } 240 241 // TODO: here we can optimize document part doesn't need to be read 242 DocumentModel newModel = createDocumentModel(doc, docModel.getSessionId(), null); 243 newModel.copyContextData(docModel); 244 return newModel; 245 } 246 247 /** 248 * Gets what's to refresh in a model (except for the ACPs, which need the session). 249 */ 250 public static DocumentModelRefresh refreshDocumentModel(Document doc, int flags, String[] schemas) 251 throws LifeCycleException { 252 DocumentModelRefresh refresh = new DocumentModelRefresh(); 253 254 refresh.instanceFacets = new HashSet<>(Arrays.asList(doc.getFacets())); 255 Set<String> docSchemas = DocumentModelImpl.computeSchemas(doc.getType(), refresh.instanceFacets, doc.isProxy()); 256 257 if ((flags & DocumentModel.REFRESH_STATE) != 0) { 258 refresh.lifeCycleState = doc.getLifeCycleState(); 259 refresh.lifeCyclePolicy = doc.getLifeCyclePolicy(); 260 refresh.isCheckedOut = doc.isCheckedOut(); 261 refresh.isLatestVersion = doc.isLatestVersion(); 262 refresh.isMajorVersion = doc.isMajorVersion(); 263 refresh.isLatestMajorVersion = doc.isLatestMajorVersion(); 264 refresh.isVersionSeriesCheckedOut = doc.isVersionSeriesCheckedOut(); 265 refresh.versionSeriesId = doc.getVersionSeriesId(); 266 refresh.checkinComment = doc.getCheckinComment(); 267 } 268 269 if ((flags & DocumentModel.REFRESH_CONTENT) != 0) { 270 if (schemas == null) { 271 schemas = docSchemas.toArray(new String[0]); 272 } 273 TypeProvider typeProvider = Framework.getService(SchemaManager.class); 274 DocumentPart[] parts = new DocumentPart[schemas.length]; 275 for (int i = 0; i < schemas.length; i++) { 276 DocumentPart part = new DocumentPartImpl(typeProvider.getSchema(schemas[i])); 277 doc.readDocumentPart(part); 278 parts[i] = part; 279 } 280 refresh.documentParts = parts; 281 } 282 283 return refresh; 284 } 285 286 /** 287 * Create an empty documentmodel for a given type with its id already setted. This can be useful when trying to 288 * attach a documentmodel that has been serialized and modified. 289 * 290 * @since 5.7.2 291 */ 292 public static DocumentModel createDocumentModel(String type, String id) { 293 SchemaManager sm = Framework.getService(SchemaManager.class); 294 DocumentType docType = sm.getDocumentType(type); 295 DocumentModel doc = new DocumentModelImpl(null, docType.getName(), id, null, null, new IdRef(id), null, null, 296 null, null, null); 297 for (Schema schema : docType.getSchemas()) { 298 ((DocumentModelImpl) doc).addDataModel(createDataModel(null, schema)); 299 } 300 return doc; 301 } 302 303}