/* * (C) Copyright 2012-2016 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: * bstefanescu */ package org.nuxeo.ecm.core.io.impl; import java.io.IOException; import java.util.Calendar; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Random; import org.apache.commons.codec.binary.Base64; import org.apache.commons.lang.StringUtils; import org.dom4j.Document; import org.dom4j.DocumentFactory; import org.dom4j.Element; import org.dom4j.QName; import org.nuxeo.common.collections.PrimitiveArrays; import org.nuxeo.common.utils.Path; import org.nuxeo.ecm.core.api.Blob; import org.nuxeo.ecm.core.api.DataModel; import org.nuxeo.ecm.core.api.DocumentLocation; import org.nuxeo.ecm.core.api.DocumentModel; import org.nuxeo.ecm.core.api.IdRef; import org.nuxeo.ecm.core.api.impl.DocumentLocationImpl; import org.nuxeo.ecm.core.api.security.ACE; import org.nuxeo.ecm.core.api.security.ACL; import org.nuxeo.ecm.core.api.security.ACP; import org.nuxeo.ecm.core.io.ExportConstants; import org.nuxeo.ecm.core.io.ExportedDocument; import org.nuxeo.ecm.core.schema.Namespace; import org.nuxeo.ecm.core.schema.SchemaManager; import org.nuxeo.ecm.core.schema.TypeConstants; import org.nuxeo.ecm.core.schema.types.ComplexType; 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.ecm.core.schema.utils.DateParser; import org.nuxeo.runtime.api.Framework; /** * A representation for an exported document. * <p> * It contains all the information needed to restore document data and state. * * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a> */ public class ExportedDocumentImpl implements ExportedDocument { private static final Random random = new Random(); protected DocumentLocation srcLocation; // document unique ID protected String id; // document path protected Path path; // the main document protected Document document; // the external blobs if any protected final Map<String, Blob> blobs = new HashMap<>(4); // the optional attached documents protected final Map<String, Document> documents = new HashMap<>(4); public ExportedDocumentImpl() { } /** * @param path the path to use for this document this is used to remove full paths */ public ExportedDocumentImpl(DocumentModel doc, Path path, boolean inlineBlobs) throws IOException { id = doc.getId(); if (path == null) { this.path = new Path(""); } else { this.path = path.makeRelative(); } readDocument(doc, inlineBlobs); srcLocation = new DocumentLocationImpl(doc); } public ExportedDocumentImpl(DocumentModel doc) throws IOException { this(doc, false); } public ExportedDocumentImpl(DocumentModel doc, boolean inlineBlobs) throws IOException { this(doc, doc.getPath(), inlineBlobs); } /** * @return the source DocumentLocation */ @Override public DocumentLocation getSourceLocation() { return srcLocation; } @Override public Path getPath() { return path; } @Override public void setPath(Path path) { this.path = path; } @Override public String getId() { return id; } @Override public void setId(String id) { this.id = id; } @Override public String getType() { return document.getRootElement().element(ExportConstants.SYSTEM_TAG).elementText("type"); } @Override public Document getDocument() { return document; } @Override public void setDocument(Document document) { this.document = document; id = document.getRootElement().attributeValue(ExportConstants.ID_ATTR); String repName = document.getRootElement().attributeValue(ExportConstants.REP_NAME); srcLocation = new DocumentLocationImpl(repName, new IdRef(id)); } @Override public Map<String, Blob> getBlobs() { return blobs; } @Override public void putBlob(String blobId, Blob blob) { blobs.put(blobId, blob); } @Override public Blob removeBlob(String blobId) { return blobs.remove(blobId); } @Override public Blob getBlob(String blobId) { return blobs.get(blobId); } @Override public boolean hasExternalBlobs() { return !blobs.isEmpty(); } @Override public Map<String, Document> getDocuments() { return documents; } @Override public Document getDocument(String docId) { return documents.get(docId); } @Override public void putDocument(String docId, Document doc) { documents.put(docId, doc); } @Override public Document removeDocument(String docId) { return documents.remove(docId); } /** * @return the number of files describing the document. */ @Override public int getFilesCount() { return 1 + documents.size() + blobs.size(); } protected void readDocument(DocumentModel doc, boolean inlineBlobs) throws IOException { document = DocumentFactory.getInstance().createDocument(); document.setName(doc.getName()); Element rootElement = document.addElement(ExportConstants.DOCUMENT_TAG); rootElement.addAttribute(ExportConstants.REP_NAME, doc.getRepositoryName()); rootElement.addAttribute(ExportConstants.ID_ATTR, doc.getRef().toString()); Element systemElement = rootElement.addElement(ExportConstants.SYSTEM_TAG); systemElement.addElement(ExportConstants.TYPE_TAG).addText(doc.getType()); systemElement.addElement(ExportConstants.PATH_TAG).addText(path.toString()); // lifecycle readLifeCycleInfo(systemElement, doc); // facets readFacets(systemElement, doc); // write security Element acpElement = systemElement.addElement(ExportConstants.ACCESS_CONTROL_TAG); ACP acp = doc.getACP(); if (acp != null) { readACP(acpElement, acp); } // write schemas readDocumentSchemas(rootElement, doc, inlineBlobs); } protected void readLifeCycleInfo(Element element, DocumentModel doc) { String lifeCycleState = doc.getCurrentLifeCycleState(); if (lifeCycleState != null && lifeCycleState.length() > 0) { element.addElement(ExportConstants.LIFECYCLE_STATE_TAG).addText(lifeCycleState); } String lifeCyclePolicy = doc.getLifeCyclePolicy(); if (lifeCyclePolicy != null && lifeCyclePolicy.length() > 0) { element.addElement(ExportConstants.LIFECYCLE_POLICY_TAG).addText(lifeCyclePolicy); } } protected void readFacets(Element element, DocumentModel doc) { // facets for (String facet : doc.getFacets()) { element.addElement(ExportConstants.FACET_TAG).addText(facet); } } protected void readDocumentSchemas(Element element, DocumentModel doc, boolean inlineBlobs) throws IOException { SchemaManager schemaManager = Framework.getLocalService(SchemaManager.class); String[] schemaNames = doc.getSchemas(); for (String schemaName : schemaNames) { Element schemaElement = element.addElement(ExportConstants.SCHEMA_TAG).addAttribute("name", schemaName); Schema schema = schemaManager.getSchema(schemaName); Namespace targetNs = schema.getNamespace(); // If namespace prefix is empty, use schema name if (StringUtils.isEmpty(targetNs.prefix)) { targetNs = new Namespace(targetNs.uri, schema.getName()); } schemaElement.addNamespace(targetNs.prefix, targetNs.uri); DataModel dataModel = doc.getDataModel(schemaName); for (Field field : schema.getFields()) { Object value = dataModel.getData(field.getName().getLocalName()); readProperty(schemaElement, targetNs, field, value, inlineBlobs); } } } protected void readProperty(Element parent, Namespace targetNs, Field field, Object value, boolean inlineBlobs) throws IOException { if (value == null) { return; // have no content } Type type = field.getType(); QName name = QName.get(field.getName().getLocalName(), targetNs.prefix, targetNs.uri); Element element = parent.addElement(name); // extract the element content if (type.isSimpleType()) { // use CDATA to avoid any bad interaction between content and envelope String encodedValue = type.encode(value); if (encodedValue != null) { // workaround embedded CDATA encodedValue = encodedValue.replaceAll("]]>", "]]]]><![CDATA[>"); } element.addCDATA(encodedValue); } else if (type.isComplexType()) { ComplexType ctype = (ComplexType) type; if (TypeConstants.isContentType(ctype)) { readBlob(element, ctype, (Blob) value, inlineBlobs); } else { readComplex(element, ctype, (Map) value, inlineBlobs); } } else if (type.isListType()) { if (value instanceof List) { readList(element, (ListType) type, (List) value, inlineBlobs); } else if (value.getClass().getComponentType() != null) { readList(element, (ListType) type, PrimitiveArrays.toList(value), inlineBlobs); } else { throw new IllegalArgumentException("A value of list type is neither list neither array: " + value); } } } protected final void readBlob(Element element, ComplexType ctype, Blob blob, boolean inlineBlobs) throws IOException { String blobPath = Integer.toHexString(random.nextInt()) + ".blob"; element.addElement(ExportConstants.BLOB_ENCODING).addText(blob.getEncoding() != null ? blob.getEncoding() : ""); element.addElement(ExportConstants.BLOB_MIME_TYPE) .addText(blob.getMimeType() != null ? blob.getMimeType() : ""); element.addElement(ExportConstants.BLOB_FILENAME).addText(blob.getFilename() != null ? blob.getFilename() : ""); Element data = element.addElement(ExportConstants.BLOB_DATA); if (inlineBlobs) { String content = Base64.encodeBase64String(blob.getByteArray()); data.setText(content); } else { data.setText(blobPath); blobs.put(blobPath, blob); } element.addElement(ExportConstants.BLOB_DIGEST).addText(blob.getDigest() != null ? blob.getDigest() : ""); } protected final void readComplex(Element element, ComplexType ctype, Map map, boolean inlineBlobs) throws IOException { Iterator<Map.Entry> it = map.entrySet().iterator(); while (it.hasNext()) { Map.Entry entry = it.next(); readProperty(element, ctype.getNamespace(), ctype.getField(entry.getKey().toString()), entry.getValue(), inlineBlobs); } } protected final void readList(Element element, ListType ltype, List list, boolean inlineBlobs) throws IOException { Field field = ltype.getField(); for (Object obj : list) { readProperty(element, Namespace.DEFAULT_NS, field, obj, inlineBlobs); } } protected static void readACP(Element element, ACP acp) { ACL[] acls = acp.getACLs(); for (ACL acl : acls) { Element aclElement = element.addElement(ExportConstants.ACL_TAG); aclElement.addAttribute(ExportConstants.NAME_ATTR, acl.getName()); ACE[] aces = acl.getACEs(); for (ACE ace : aces) { Element aceElement = aclElement.addElement(ExportConstants.ACE_TAG); aceElement.addAttribute(ExportConstants.PRINCIPAL_ATTR, ace.getUsername()); aceElement.addAttribute(ExportConstants.PERMISSION_ATTR, ace.getPermission()); aceElement.addAttribute(ExportConstants.GRANT_ATTR, String.valueOf(ace.isGranted())); aceElement.addAttribute(ExportConstants.CREATOR_ATTR, ace.getCreator()); Calendar begin = ace.getBegin(); if (begin != null) { aceElement.addAttribute(ExportConstants.BEGIN_ATTR, DateParser.formatW3CDateTime((begin).getTime())); } Calendar end = ace.getEnd(); if (end != null) { aceElement.addAttribute(ExportConstants.END_ATTR, DateParser.formatW3CDateTime((end).getTime())); } } } } }