/* * (C) Copyright 2006-2014 Nuxeo SA (http://nuxeo.com/) and others. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Contributors: * Bogdan Stefanescu * Florent Guillaume */ package org.nuxeo.ecm.core.api; import java.io.Serializable; import java.util.Arrays; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.nuxeo.common.utils.Path; import org.nuxeo.ecm.core.api.DocumentModel.DocumentModelRefresh; import org.nuxeo.ecm.core.api.impl.DataModelImpl; import org.nuxeo.ecm.core.api.impl.DocumentModelImpl; import org.nuxeo.ecm.core.api.model.DocumentPart; import org.nuxeo.ecm.core.api.model.impl.DocumentPartImpl; import org.nuxeo.ecm.core.model.Document; import org.nuxeo.ecm.core.model.Document.WriteContext; import org.nuxeo.ecm.core.schema.DocumentType; import org.nuxeo.ecm.core.schema.FacetNames; import org.nuxeo.ecm.core.schema.Prefetch; import org.nuxeo.ecm.core.schema.PrefetchInfo; import org.nuxeo.ecm.core.schema.SchemaManager; import org.nuxeo.ecm.core.schema.TypeProvider; import org.nuxeo.ecm.core.schema.types.Field; import org.nuxeo.ecm.core.schema.types.ListType; import org.nuxeo.ecm.core.schema.types.Schema; import org.nuxeo.ecm.core.schema.types.Type; import org.nuxeo.runtime.api.Framework; /** * Bridge between a {@link DocumentModel} and a {@link Document} for creation / update. */ public class DocumentModelFactory { private static final Log log = LogFactory.getLog(DocumentModelFactory.class); // Utility class. private DocumentModelFactory() { } /** * Creates a document model for an existing document. * * @param doc the document * @param sid the session id for this document * @param schemas the schemas to prefetch (deprecated), or {@code null} * @return the new document model */ public static DocumentModelImpl createDocumentModel(Document doc, String sid, String[] schemas) { DocumentType type = doc.getType(); if (type == null) { throw new NuxeoException("Type not found for doc " + doc); } DocumentRef docRef = new IdRef(doc.getUUID()); Document parent = doc.getParent(); DocumentRef parentRef = parent == null ? null : new IdRef(parent.getUUID()); // Compute document source id if exists Document sourceDoc = doc.getSourceDocument(); String sourceId = sourceDoc == null ? null : sourceDoc.getUUID(); // Immutable flag boolean immutable = doc.isVersion() || (doc.isProxy() && sourceDoc.isVersion()); // Instance facets Set<String> facets = new HashSet<String>(Arrays.asList(doc.getFacets())); if (immutable) { facets.add(FacetNames.IMMUTABLE); } // Compute repository name. String repositoryName = doc.getRepositoryName(); // versions being imported before their live doc don't have a path String p = doc.getPath(); Path path = p == null ? null : new Path(p); // create the document model // lock is unused DocumentModelImpl docModel = new DocumentModelImpl(sid, type.getName(), doc.getUUID(), path, docRef, parentRef, null, facets, sourceId, repositoryName, doc.isProxy()); docModel.setPosInternal(doc.getPos()); if (doc.isVersion()) { docModel.setIsVersion(true); } if (immutable) { docModel.setIsImmutable(true); } // populate prefetch PrefetchInfo prefetchInfo = type.getPrefetchInfo(); Prefetch prefetch; String[] prefetchSchemas; if (prefetchInfo != null) { Set<String> docSchemas = new HashSet<String>(Arrays.asList(docModel.getSchemas())); prefetch = getPrefetch(doc, prefetchInfo, docSchemas); prefetchSchemas = prefetchInfo.getSchemas(); } else { prefetch = null; prefetchSchemas = null; } // populate datamodels List<String> loadSchemas = new LinkedList<String>(); if (schemas == null) { schemas = prefetchSchemas; } if (schemas != null) { Set<String> validSchemas = new HashSet<String>(Arrays.asList(docModel.getSchemas())); for (String schemaName : schemas) { if (validSchemas.contains(schemaName)) { loadSchemas.add(schemaName); } } } SchemaManager schemaManager = Framework.getLocalService(SchemaManager.class); for (String schemaName : loadSchemas) { Schema schema = schemaManager.getSchema(schemaName); docModel.addDataModel(createDataModel(doc, schema)); } if (prefetch != null) { // ignore prefetches already loaded as datamodels for (String schemaName : loadSchemas) { prefetch.clearPrefetch(schemaName); } // set prefetch docModel.setPrefetch(prefetch); } // prefetch lifecycle state try { String lifeCycleState = doc.getLifeCycleState(); docModel.prefetchCurrentLifecycleState(lifeCycleState); String lifeCyclePolicy = doc.getLifeCyclePolicy(); docModel.prefetchLifeCyclePolicy(lifeCyclePolicy); } catch (LifeCycleException e) { log.debug("Cannot prefetch lifecycle for doc: " + doc.getName() + ". Error: " + e.getMessage()); } return docModel; } /** * Returns a document model computed from its type, querying the {@link SchemaManager} service. * <p> * The created document model is not linked to any core session. * * @since 5.4.2 */ public static DocumentModelImpl createDocumentModel(String docType) { SchemaManager schemaManager = Framework.getLocalService(SchemaManager.class); DocumentType type = schemaManager.getDocumentType(docType); return createDocumentModel(null, type); } /** * Creates a document model for a new document. * <p> * Initializes the proper data models according to the type info. * * @param sessionId the CoreSession id * @param docType the document type * @return the document model */ public static DocumentModelImpl createDocumentModel(String sessionId, DocumentType docType) { DocumentModelImpl docModel = new DocumentModelImpl(sessionId, docType.getName(), null, null, null, null, null, null, null, null, null); for (Schema schema : docType.getSchemas()) { docModel.addDataModel(createDataModel(null, schema)); } return docModel; } /** * Creates a data model from a document and a schema. If the document is null, just creates empty data models. */ public static DataModel createDataModel(Document doc, Schema schema) { DocumentPart part = new DocumentPartImpl(schema); if (doc != null) { doc.readDocumentPart(part); } return new DataModelImpl(part); } /** * Writes a document model to a document. Returns the re-read document model. */ public static DocumentModel writeDocumentModel(DocumentModel docModel, Document doc) { if (!(docModel instanceof DocumentModelImpl)) { throw new NuxeoException("Must be a DocumentModelImpl: " + docModel); } boolean changed = false; // change token String token = (String) docModel.getContextData(CoreSession.CHANGE_TOKEN); String currentToken; if (token != null && (currentToken = doc.getChangeToken()) != null && !currentToken.equals(token)) { throw new ConcurrentUpdateException(doc.getUUID()); } // facets added/removed Set<String> instanceFacets = ((DocumentModelImpl) docModel).instanceFacets; Set<String> instanceFacetsOrig = ((DocumentModelImpl) docModel).instanceFacetsOrig; Set<String> addedFacets = new HashSet<String>(instanceFacets); addedFacets.removeAll(instanceFacetsOrig); for (String facet : addedFacets) { changed = doc.addFacet(facet) || changed; } Set<String> removedFacets = new HashSet<String>(instanceFacetsOrig); removedFacets.removeAll(instanceFacets); for (String facet : removedFacets) { changed = doc.removeFacet(facet) || changed; } // write data models // check only the loaded ones to find the dirty ones WriteContext writeContext = doc.getWriteContext(); for (DataModel dm : docModel.getDataModelsCollection()) { // only loaded if (dm.isDirty()) { DocumentPart part = ((DataModelImpl) dm).getDocumentPart(); changed = doc.writeDocumentPart(part, writeContext) || changed; } } // write the blobs last, so that blob providers have access to the new doc state writeContext.flush(doc); if (!changed) { return docModel; } // TODO: here we can optimize document part doesn't need to be read DocumentModel newModel = createDocumentModel(doc, docModel.getSessionId(), null); newModel.copyContextData(docModel); return newModel; } /** * Gets what's to refresh in a model (except for the ACPs, which need the session). */ public static DocumentModelRefresh refreshDocumentModel(Document doc, int flags, String[] schemas) throws LifeCycleException { DocumentModelRefresh refresh = new DocumentModelRefresh(); refresh.instanceFacets = new HashSet<String>(Arrays.asList(doc.getFacets())); Set<String> docSchemas = DocumentModelImpl.computeSchemas(doc.getType(), refresh.instanceFacets, doc.isProxy()); if ((flags & DocumentModel.REFRESH_PREFETCH) != 0) { PrefetchInfo prefetchInfo = doc.getType().getPrefetchInfo(); if (prefetchInfo != null) { refresh.prefetch = getPrefetch(doc, prefetchInfo, docSchemas); } } if ((flags & DocumentModel.REFRESH_STATE) != 0) { refresh.lifeCycleState = doc.getLifeCycleState(); refresh.lifeCyclePolicy = doc.getLifeCyclePolicy(); refresh.isCheckedOut = doc.isCheckedOut(); refresh.isLatestVersion = doc.isLatestVersion(); refresh.isMajorVersion = doc.isMajorVersion(); refresh.isLatestMajorVersion = doc.isLatestMajorVersion(); refresh.isVersionSeriesCheckedOut = doc.isVersionSeriesCheckedOut(); refresh.versionSeriesId = doc.getVersionSeriesId(); refresh.checkinComment = doc.getCheckinComment(); } if ((flags & DocumentModel.REFRESH_CONTENT) != 0) { if (schemas == null) { schemas = docSchemas.toArray(new String[0]); } TypeProvider typeProvider = Framework.getLocalService(SchemaManager.class); DocumentPart[] parts = new DocumentPart[schemas.length]; for (int i = 0; i < schemas.length; i++) { DocumentPart part = new DocumentPartImpl(typeProvider.getSchema(schemas[i])); doc.readDocumentPart(part); parts[i] = part; } refresh.documentParts = parts; } return refresh; } /** * Prefetches from a document. */ protected static Prefetch getPrefetch(Document doc, PrefetchInfo prefetchInfo, Set<String> docSchemas) { SchemaManager schemaManager = Framework.getLocalService(SchemaManager.class); // individual fields Set<String> xpaths = new HashSet<String>(); String[] prefetchFields = prefetchInfo.getFields(); if (prefetchFields != null) { xpaths.addAll(Arrays.asList(prefetchFields)); } // whole schemas (but NOT their complex properties) String[] prefetchSchemas = prefetchInfo.getSchemas(); if (prefetchSchemas != null) { for (String schemaName : prefetchSchemas) { if (docSchemas.contains(schemaName)) { Schema schema = schemaManager.getSchema(schemaName); if (schema != null) { for (Field field : schema.getFields()) { if (isScalarField(field)) { xpaths.add(field.getName().getPrefixedName()); } } } } } } // do the prefetch Prefetch prefetch = new Prefetch(); for (String schemaName : docSchemas) { Schema schema = schemaManager.getSchema(schemaName); // find xpaths for this schema Set<String> schemaXpaths = new HashSet<String>(); for (String xpath : xpaths) { String sn = DocumentModelImpl.getXPathSchemaName(xpath, docSchemas, null); if (schemaName.equals(sn)) { schemaXpaths.add(xpath); } } if (schemaXpaths.isEmpty()) { continue; } Map<String, Serializable> map = doc.readPrefetch(schema, schemaXpaths); for (Entry<String, Serializable> en : map.entrySet()) { String xpath = en.getKey(); Serializable value = en.getValue(); String[] returnName = new String[1]; String sn = DocumentModelImpl.getXPathSchemaName(xpath, docSchemas, returnName); String name = returnName[0]; prefetch.put(xpath, sn, name, value); } } return prefetch; } /** * Checks if a field is a primitive type or array. */ protected static boolean isScalarField(Field field) { Type type = field.getType(); if (type.isComplexType()) { // complex type return false; } if (!type.isListType()) { // primitive type return true; } // array or complex list? return ((ListType) type).getFieldType().isSimpleType(); } /** * Create an empty documentmodel for a given type with its id already setted. This can be useful when trying to * attach a documentmodel that has been serialized and modified. * * @param type * @param id * @return * @since 5.7.2 */ public static DocumentModel createDocumentModel(String type, String id) { SchemaManager sm = Framework.getLocalService(SchemaManager.class); DocumentType docType = sm.getDocumentType(type); DocumentModel doc = new DocumentModelImpl(null, docType.getName(), id, null, null, new IdRef(id), null, null, null, null, null); for (Schema schema : docType.getSchemas()) { ((DocumentModelImpl) doc).addDataModel(createDataModel(null, schema)); } return doc; } }