001/*
002 * Copyright (c) 2012 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 *     bstefanescu
011 *
012 * $Id: ExportedDocumentImpl.java 29029 2008-01-14 18:38:14Z ldoguin $
013 */
014
015package org.nuxeo.ecm.core.io.impl;
016
017import java.io.IOException;
018import java.util.Calendar;
019import java.util.HashMap;
020import java.util.Iterator;
021import java.util.List;
022import java.util.Map;
023import java.util.Random;
024
025import org.apache.commons.lang.StringUtils;
026import org.apache.commons.logging.Log;
027import org.apache.commons.logging.LogFactory;
028import org.dom4j.Document;
029import org.dom4j.DocumentFactory;
030import org.dom4j.Element;
031import org.dom4j.QName;
032import org.nuxeo.common.collections.PrimitiveArrays;
033import org.nuxeo.common.utils.Base64;
034import org.nuxeo.common.utils.Path;
035import org.nuxeo.ecm.core.api.Blob;
036import org.nuxeo.ecm.core.api.DataModel;
037import org.nuxeo.ecm.core.api.DocumentLocation;
038import org.nuxeo.ecm.core.api.DocumentModel;
039import org.nuxeo.ecm.core.api.IdRef;
040import org.nuxeo.ecm.core.api.impl.DocumentLocationImpl;
041import org.nuxeo.ecm.core.api.security.ACE;
042import org.nuxeo.ecm.core.api.security.ACL;
043import org.nuxeo.ecm.core.api.security.ACP;
044import org.nuxeo.ecm.core.io.ExportConstants;
045import org.nuxeo.ecm.core.io.ExportedDocument;
046import org.nuxeo.ecm.core.schema.Namespace;
047import org.nuxeo.ecm.core.schema.SchemaManager;
048import org.nuxeo.ecm.core.schema.TypeConstants;
049import org.nuxeo.ecm.core.schema.types.ComplexType;
050import org.nuxeo.ecm.core.schema.types.Field;
051import org.nuxeo.ecm.core.schema.types.ListType;
052import org.nuxeo.ecm.core.schema.types.Schema;
053import org.nuxeo.ecm.core.schema.types.Type;
054import org.nuxeo.ecm.core.schema.utils.DateParser;
055import org.nuxeo.runtime.api.Framework;
056
057/**
058 * A representation for an exported document.
059 * <p>
060 * It contains all the information needed to restore document data and state.
061 *
062 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
063 */
064@SuppressWarnings("unchecked")
065public class ExportedDocumentImpl implements ExportedDocument {
066
067    private static final Log log = LogFactory.getLog(ExportedDocumentImpl.class);
068
069    private static final Random random = new Random();
070
071    protected DocumentLocation srcLocation;
072
073    // document unique ID
074    protected String id;
075
076    // document path
077    protected Path path;
078
079    // the main document
080    protected Document document;
081
082    // the external blobs if any
083    protected final Map<String, Blob> blobs = new HashMap<String, Blob>(4);
084
085    // the optional attached documents
086    protected final Map<String, Document> documents = new HashMap<String, Document>(4);
087
088    public ExportedDocumentImpl() {
089    }
090
091    /**
092     * @param doc
093     * @param path the path to use for this document this is used to remove full paths
094     */
095    public ExportedDocumentImpl(DocumentModel doc, Path path, boolean inlineBlobs) throws IOException {
096        id = doc.getId();
097        if (path == null) {
098            this.path = new Path("");
099        } else {
100            this.path = path.makeRelative();
101        }
102        readDocument(doc, inlineBlobs);
103        srcLocation = new DocumentLocationImpl(doc);
104    }
105
106    public ExportedDocumentImpl(DocumentModel doc) throws IOException {
107        this(doc, false);
108    }
109
110    public ExportedDocumentImpl(DocumentModel doc, boolean inlineBlobs) throws IOException {
111        this(doc, doc.getPath(), inlineBlobs);
112    }
113
114    /**
115     * @return the source DocumentLocation
116     */
117    @Override
118    public DocumentLocation getSourceLocation() {
119        return srcLocation;
120    }
121
122    @Override
123    public Path getPath() {
124        return path;
125    }
126
127    @Override
128    public void setPath(Path path) {
129        this.path = path;
130    }
131
132    @Override
133    public String getId() {
134        return id;
135    }
136
137    @Override
138    public void setId(String id) {
139        this.id = id;
140    }
141
142    @Override
143    public String getType() {
144        return document.getRootElement().element(ExportConstants.SYSTEM_TAG).elementText("type");
145    }
146
147    @Override
148    public Document getDocument() {
149        return document;
150    }
151
152    @Override
153    public void setDocument(Document document) {
154        this.document = document;
155        id = document.getRootElement().attributeValue(ExportConstants.ID_ATTR);
156        String repName = document.getRootElement().attributeValue(ExportConstants.REP_NAME);
157        srcLocation = new DocumentLocationImpl(repName, new IdRef(id));
158    }
159
160    @Override
161    public Map<String, Blob> getBlobs() {
162        return blobs;
163    }
164
165    @Override
166    public void putBlob(String id, Blob blob) {
167        blobs.put(id, blob);
168    }
169
170    @Override
171    public Blob removeBlob(String id) {
172        return blobs.remove(id);
173    }
174
175    @Override
176    public Blob getBlob(String id) {
177        return blobs.get(id);
178    }
179
180    @Override
181    public boolean hasExternalBlobs() {
182        return !blobs.isEmpty();
183    }
184
185    @Override
186    public Map<String, Document> getDocuments() {
187        return documents;
188    }
189
190    @Override
191    public Document getDocument(String id) {
192        return documents.get(id);
193    }
194
195    @Override
196    public void putDocument(String id, Document doc) {
197        documents.put(id, doc);
198    }
199
200    @Override
201    public Document removeDocument(String id) {
202        return documents.remove(id);
203    }
204
205    /**
206     * @return the number of files describing the document.
207     */
208    @Override
209    public int getFilesCount() {
210        return 1 + documents.size() + blobs.size();
211    }
212
213    protected void readDocument(DocumentModel doc, boolean inlineBlobs) throws IOException {
214        document = DocumentFactory.getInstance().createDocument();
215        document.setName(doc.getName());
216        Element rootElement = document.addElement(ExportConstants.DOCUMENT_TAG);
217        rootElement.addAttribute(ExportConstants.REP_NAME, doc.getRepositoryName());
218        rootElement.addAttribute(ExportConstants.ID_ATTR, doc.getRef().toString());
219        Element systemElement = rootElement.addElement(ExportConstants.SYSTEM_TAG);
220        systemElement.addElement(ExportConstants.TYPE_TAG).addText(doc.getType());
221        systemElement.addElement(ExportConstants.PATH_TAG).addText(path.toString());
222        // lifecycle
223        readLifeCycleInfo(systemElement, doc);
224
225        // facets
226        readFacets(systemElement, doc);
227        // write security
228        Element acpElement = systemElement.addElement(ExportConstants.ACCESS_CONTROL_TAG);
229        ACP acp = doc.getACP();
230        if (acp != null) {
231            readACP(acpElement, acp);
232        }
233        // write schemas
234        readDocumentSchemas(rootElement, doc, inlineBlobs);
235    }
236
237    protected void readLifeCycleInfo(Element element, DocumentModel doc) {
238        String lifeCycleState = doc.getCurrentLifeCycleState();
239        if (lifeCycleState != null && lifeCycleState.length() > 0) {
240            element.addElement(ExportConstants.LIFECYCLE_STATE_TAG).addText(lifeCycleState);
241        }
242        String lifeCyclePolicy = doc.getLifeCyclePolicy();
243        if (lifeCyclePolicy != null && lifeCyclePolicy.length() > 0) {
244            element.addElement(ExportConstants.LIFECYCLE_POLICY_TAG).addText(lifeCyclePolicy);
245        }
246    }
247
248    protected void readFacets(Element element, DocumentModel doc) {
249        // facets
250        for (String facet : doc.getFacets()) {
251            element.addElement(ExportConstants.FACET_TAG).addText(facet);
252        }
253    }
254
255    protected void readDocumentSchemas(Element element, DocumentModel doc, boolean inlineBlobs) throws IOException {
256        SchemaManager schemaManager = Framework.getLocalService(SchemaManager.class);
257        String[] schemaNames = doc.getSchemas();
258        for (String schemaName : schemaNames) {
259            Element schemaElement = element.addElement(ExportConstants.SCHEMA_TAG).addAttribute("name", schemaName);
260            Schema schema = schemaManager.getSchema(schemaName);
261            Namespace targetNs = schema.getNamespace();
262            // If namespace prefix is empty, use schema name
263            if (StringUtils.isEmpty(targetNs.prefix)) {
264                targetNs = new Namespace(targetNs.uri, schema.getName());
265            }
266            schemaElement.addNamespace(targetNs.prefix, targetNs.uri);
267            DataModel dataModel = doc.getDataModel(schemaName);
268            for (Field field : schema.getFields()) {
269                Object value = dataModel.getData(field.getName().getLocalName());
270                readProperty(schemaElement, targetNs, field, value, inlineBlobs);
271            }
272        }
273
274    }
275
276    protected void readProperty(Element parent, Namespace targetNs, Field field, Object value, boolean inlineBlobs)
277            throws IOException {
278        if (value == null) {
279            return; // have no content
280        }
281        Type type = field.getType();
282        QName name = QName.get(field.getName().getLocalName(), targetNs.prefix, targetNs.uri);
283        Element element = parent.addElement(name);
284
285        // extract the element content
286        if (type.isSimpleType()) {
287            // use CDATA to avoid any bad interaction between content and
288            // envelope
289            element.addCDATA(type.encode(value));
290        } else if (type.isComplexType()) {
291            ComplexType ctype = (ComplexType) type;
292            if (TypeConstants.isContentType(ctype)) {
293                readBlob(element, ctype, (Blob) value, inlineBlobs);
294            } else {
295                readComplex(element, ctype, (Map) value, inlineBlobs);
296            }
297        } else if (type.isListType()) {
298            if (value instanceof List) {
299                readList(element, (ListType) type, (List) value, inlineBlobs);
300            } else if (value.getClass().getComponentType() != null) {
301                readList(element, (ListType) type, PrimitiveArrays.toList(value), inlineBlobs);
302            } else {
303                throw new IllegalArgumentException("A value of list type is neither list neither array: " + value);
304            }
305        }
306    }
307
308    protected final void readBlob(Element element, ComplexType ctype, Blob blob, boolean inlineBlobs)
309            throws IOException {
310        String blobPath = Integer.toHexString(random.nextInt()) + ".blob";
311        element.addElement(ExportConstants.BLOB_ENCODING).addText(blob.getEncoding() != null ? blob.getEncoding() : "");
312        element.addElement(ExportConstants.BLOB_MIME_TYPE)
313               .addText(blob.getMimeType() != null ? blob.getMimeType() : "");
314        element.addElement(ExportConstants.BLOB_FILENAME).addText(blob.getFilename() != null ? blob.getFilename() : "");
315        Element data = element.addElement(ExportConstants.BLOB_DATA);
316        if (inlineBlobs) {
317            String content = Base64.encodeBytes(blob.getByteArray());
318            data.setText(content);
319        } else {
320            data.setText(blobPath);
321            blobs.put(blobPath, blob);
322        }
323        element.addElement(ExportConstants.BLOB_DIGEST).addText(blob.getDigest() != null ? blob.getDigest() : "");
324    }
325
326    protected final void readComplex(Element element, ComplexType ctype, Map map, boolean inlineBlobs)
327            throws IOException {
328        Iterator<Map.Entry> it = map.entrySet().iterator();
329        while (it.hasNext()) {
330            Map.Entry entry = it.next();
331            readProperty(element, ctype.getNamespace(), ctype.getField(entry.getKey().toString()), entry.getValue(),
332                    inlineBlobs);
333        }
334    }
335
336    protected final void readList(Element element, ListType ltype, List list, boolean inlineBlobs) throws IOException {
337        Field field = ltype.getField();
338        for (Object obj : list) {
339            readProperty(element, Namespace.DEFAULT_NS, field, obj, inlineBlobs);
340        }
341    }
342
343    protected static void readACP(Element element, ACP acp) {
344        ACL[] acls = acp.getACLs();
345        for (ACL acl : acls) {
346            Element aclElement = element.addElement(ExportConstants.ACL_TAG);
347            aclElement.addAttribute(ExportConstants.NAME_ATTR, acl.getName());
348            ACE[] aces = acl.getACEs();
349            for (ACE ace : aces) {
350                Element aceElement = aclElement.addElement(ExportConstants.ACE_TAG);
351                aceElement.addAttribute(ExportConstants.PRINCIPAL_ATTR, ace.getUsername());
352                aceElement.addAttribute(ExportConstants.PERMISSION_ATTR, ace.getPermission());
353                aceElement.addAttribute(ExportConstants.GRANT_ATTR, String.valueOf(ace.isGranted()));
354                aceElement.addAttribute(ExportConstants.CREATOR_ATTR, ace.getCreator());
355                Calendar begin = ace.getBegin();
356                if (begin != null) {
357                    aceElement.addAttribute(ExportConstants.BEGIN_ATTR, DateParser.formatW3CDateTime((begin).getTime()));
358                }
359                Calendar end = ace.getEnd();
360                if (end != null) {
361                    aceElement.addAttribute(ExportConstants.END_ATTR, DateParser.formatW3CDateTime((end).getTime()));
362                }
363            }
364        }
365    }
366
367}