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}