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