001/* 002 * Copyright (c) 2006-2011 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$ 013 */ 014 015package org.nuxeo.ecm.core.io.impl; 016 017import java.lang.reflect.Array; 018import java.util.ArrayList; 019import java.util.Calendar; 020import java.util.Date; 021import java.util.GregorianCalendar; 022import java.util.HashMap; 023import java.util.Iterator; 024import java.util.List; 025import java.util.Map; 026 027import org.apache.commons.logging.Log; 028import org.apache.commons.logging.LogFactory; 029import org.dom4j.Document; 030import org.dom4j.Element; 031import org.nuxeo.common.collections.PrimitiveArrays; 032import org.nuxeo.common.collections.ScopeType; 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.Blobs; 037import org.nuxeo.ecm.core.api.CoreSession; 038import org.nuxeo.ecm.core.api.DocumentLocation; 039import org.nuxeo.ecm.core.api.DocumentModel; 040import org.nuxeo.ecm.core.api.NuxeoException; 041import org.nuxeo.ecm.core.api.impl.DocumentModelImpl; 042import org.nuxeo.ecm.core.api.security.ACE; 043import org.nuxeo.ecm.core.api.security.ACL; 044import org.nuxeo.ecm.core.api.security.ACP; 045import org.nuxeo.ecm.core.api.security.impl.ACLImpl; 046import org.nuxeo.ecm.core.api.security.impl.ACPImpl; 047import org.nuxeo.ecm.core.io.ExportConstants; 048import org.nuxeo.ecm.core.io.ExportedDocument; 049import org.nuxeo.ecm.core.schema.SchemaManager; 050import org.nuxeo.ecm.core.schema.TypeConstants; 051import org.nuxeo.ecm.core.schema.types.ComplexType; 052import org.nuxeo.ecm.core.schema.types.Field; 053import org.nuxeo.ecm.core.schema.types.JavaTypes; 054import org.nuxeo.ecm.core.schema.types.ListType; 055import org.nuxeo.ecm.core.schema.types.Schema; 056import org.nuxeo.ecm.core.schema.types.Type; 057import org.nuxeo.ecm.core.schema.utils.DateParser; 058import org.nuxeo.ecm.core.versioning.VersioningService; 059import org.nuxeo.runtime.api.Framework; 060 061/** 062 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a> 063 */ 064// TODO: improve it -> 065// modify core session to add a batch create method and use it 066public abstract class AbstractDocumentModelWriter extends AbstractDocumentWriter { 067 068 private static final Log log = LogFactory.getLog(AbstractDocumentModelWriter.class); 069 070 protected CoreSession session; 071 072 protected Path root; 073 074 private int saveInterval; 075 076 protected int unsavedDocuments = 0; 077 078 private final Map<DocumentLocation, DocumentLocation> translationMap = new HashMap<DocumentLocation, DocumentLocation>(); 079 080 /** 081 * @param session the session to the repository where to write 082 * @param parentPath where to write the tree. this document will be used as the parent of all top level documents 083 * passed as input. Note that you may have 084 */ 085 protected AbstractDocumentModelWriter(CoreSession session, String parentPath) { 086 this(session, parentPath, 10); 087 } 088 089 protected AbstractDocumentModelWriter(CoreSession session, String parentPath, int saveInterval) { 090 if (session == null) { 091 throw new IllegalArgumentException("null session"); 092 } 093 this.session = session; 094 this.saveInterval = saveInterval; 095 root = new Path(parentPath); 096 } 097 098 public Map<DocumentLocation, DocumentLocation> getTranslationMap() { 099 return translationMap; 100 } 101 102 protected void saveIfNeeded() { 103 if (unsavedDocuments >= saveInterval) { 104 session.save(); 105 unsavedDocuments = 0; 106 } 107 } 108 109 @Override 110 public void close() { 111 if (unsavedDocuments > 0) { 112 session.save(); 113 } 114 session = null; 115 root = null; 116 } 117 118 /** 119 * Creates a new document given its path. 120 * <p> 121 * The parent of this document is assumed to exist. 122 * 123 * @param xdoc the document containing 124 * @param toPath the path of the doc to create 125 */ 126 protected DocumentModel createDocument(ExportedDocument xdoc, Path toPath) { 127 Path parentPath = toPath.removeLastSegments(1); 128 String name = toPath.lastSegment(); 129 130 DocumentModel doc = new DocumentModelImpl(parentPath.toString(), name, xdoc.getType()); 131 132 // set lifecycle state at creation 133 Element system = xdoc.getDocument().getRootElement().element(ExportConstants.SYSTEM_TAG); 134 String lifeCycleState = system.element(ExportConstants.LIFECYCLE_STATE_TAG).getText(); 135 doc.putContextData("initialLifecycleState", lifeCycleState); 136 137 // loadFacets before schemas so that additional schemas are not skipped 138 loadFacetsInfo(doc, xdoc.getDocument()); 139 140 // then load schemas data 141 loadSchemas(xdoc, doc, xdoc.getDocument()); 142 143 if (doc.hasSchema("uid")) { 144 doc.putContextData(ScopeType.REQUEST, VersioningService.SKIP_VERSIONING, true); 145 } 146 147 doc = session.createDocument(doc); 148 149 // load into the document the system properties, document needs to exist 150 loadSystemInfo(doc, xdoc.getDocument()); 151 152 unsavedDocuments += 1; 153 saveIfNeeded(); 154 155 return doc; 156 } 157 158 /** 159 * Updates an existing document. 160 */ 161 protected DocumentModel updateDocument(ExportedDocument xdoc, DocumentModel doc) { 162 // load schemas data 163 loadSchemas(xdoc, doc, xdoc.getDocument()); 164 165 loadFacetsInfo(doc, xdoc.getDocument()); 166 167 doc = session.saveDocument(doc); 168 169 unsavedDocuments += 1; 170 saveIfNeeded(); 171 172 return doc; 173 } 174 175 public int getSaveInterval() { 176 return saveInterval; 177 } 178 179 public void setSaveInterval(int saveInterval) { 180 this.saveInterval = saveInterval; 181 } 182 183 @SuppressWarnings("unchecked") 184 protected boolean loadFacetsInfo(DocumentModel docModel, Document doc) { 185 boolean added = false; 186 Element system = doc.getRootElement().element(ExportConstants.SYSTEM_TAG); 187 if (system == null) { 188 return false; 189 } 190 191 Iterator<Element> facets = system.elementIterator(ExportConstants.FACET_TAG); 192 while (facets.hasNext()) { 193 Element element = facets.next(); 194 String facet = element.getTextTrim(); 195 if (!docModel.hasFacet(facet)) { 196 docModel.addFacet(facet); 197 added = true; 198 } 199 } 200 201 return added; 202 } 203 204 @SuppressWarnings("unchecked") 205 protected void loadSystemInfo(DocumentModel docModel, Document doc) { 206 Element system = doc.getRootElement().element(ExportConstants.SYSTEM_TAG); 207 208 Element accessControl = system.element(ExportConstants.ACCESS_CONTROL_TAG); 209 if (accessControl == null) { 210 return; 211 } 212 Iterator<Element> it = accessControl.elementIterator(ExportConstants.ACL_TAG); 213 while (it.hasNext()) { 214 Element element = it.next(); 215 // import only the local acl 216 if (ACL.LOCAL_ACL.equals(element.attributeValue(ExportConstants.NAME_ATTR))) { 217 // this is the local ACL - import it 218 List<Element> entries = element.elements(); 219 int size = entries.size(); 220 if (size > 0) { 221 ACP acp = new ACPImpl(); 222 ACL acl = new ACLImpl(ACL.LOCAL_ACL); 223 acp.addACL(acl); 224 for (int i = 0; i < size; i++) { 225 Element el = entries.get(i); 226 String username = el.attributeValue(ExportConstants.PRINCIPAL_ATTR); 227 String permission = el.attributeValue(ExportConstants.PERMISSION_ATTR); 228 String grant = el.attributeValue(ExportConstants.GRANT_ATTR); 229 String creator = el.attributeValue(ExportConstants.CREATOR_ATTR); 230 String beginStr = el.attributeValue(ExportConstants.BEGIN_ATTR); 231 Calendar begin = null; 232 if (beginStr != null) { 233 Date date = DateParser.parseW3CDateTime(beginStr); 234 begin = new GregorianCalendar(); 235 begin.setTimeInMillis(date.getTime()); 236 } 237 String endStr = el.attributeValue(ExportConstants.END_ATTR); 238 Calendar end = null; 239 if (endStr != null) { 240 Date date = DateParser.parseW3CDateTime(endStr); 241 end = new GregorianCalendar(); 242 end.setTimeInMillis(date.getTime()); 243 } 244 ACE ace = ACE.builder(username, permission) 245 .isGranted(Boolean.parseBoolean(grant)) 246 .creator(creator) 247 .begin(begin) 248 .end(end) 249 .build(); 250 acl.add(ace); 251 } 252 acp.addACL(acl); 253 session.setACP(docModel.getRef(), acp, false); 254 } 255 } 256 } 257 } 258 259 @SuppressWarnings("unchecked") 260 protected void loadSchemas(ExportedDocument xdoc, DocumentModel docModel, Document doc) { 261 SchemaManager schemaMgr = Framework.getLocalService(SchemaManager.class); 262 Iterator<Element> it = doc.getRootElement().elementIterator(ExportConstants.SCHEMA_TAG); 263 while (it.hasNext()) { 264 Element element = it.next(); 265 String schemaName = element.attributeValue(ExportConstants.NAME_ATTR); 266 Schema schema = schemaMgr.getSchema(schemaName); 267 if (schema == null) { 268 throw new NuxeoException("Schema not found: " + schemaName); 269 } 270 loadSchema(xdoc, schema, docModel, element); 271 } 272 } 273 274 @SuppressWarnings("unchecked") 275 protected static void loadSchema(ExportedDocument xdoc, Schema schema, DocumentModel doc, Element schemaElement) { 276 String schemaName = schemaElement.attributeValue(ExportConstants.NAME_ATTR); 277 Map<String, Object> data = new HashMap<String, Object>(); 278 Iterator<Element> it = schemaElement.elementIterator(); 279 while (it.hasNext()) { 280 Element element = it.next(); 281 String name = element.getName(); 282 Field field = schema.getField(name); 283 if (field == null) { 284 throw new NuxeoException("Invalid input document. No such property was found " + name + " in schema " 285 + schemaName); 286 } 287 Object value = getElementData(xdoc, element, field.getType()); 288 data.put(name, value); 289 } 290 doc.setProperties(schemaName, data); 291 } 292 293 protected static Class getFieldClass(Type fieldType) { 294 Class klass = JavaTypes.getClass(fieldType); 295 // for enumerated SimpleTypes we may need to lookup on the supertype 296 // we do the recursion here and not in JavaTypes to avoid potential impacts 297 if (klass == null && fieldType.getSuperType() != null) { 298 return getFieldClass(fieldType.getSuperType()); 299 } 300 return klass; 301 } 302 303 @SuppressWarnings("unchecked") 304 private static Object getElementData(ExportedDocument xdoc, Element element, Type type) { 305 // empty xml tag must be null value (not empty string) 306 if (!element.hasContent()) { 307 return null; 308 } 309 if (type.isSimpleType()) { 310 return type.decode(element.getText()); 311 } else if (type.isListType()) { 312 ListType ltype = (ListType) type; 313 List<Object> list = new ArrayList<Object>(); 314 Iterator<Element> it = element.elementIterator(); 315 while (it.hasNext()) { 316 Element el = it.next(); 317 list.add(getElementData(xdoc, el, ltype.getFieldType())); 318 } 319 Type ftype = ltype.getFieldType(); 320 if (ftype.isSimpleType()) { // these are stored as arrays 321 Class klass = getFieldClass(ftype); 322 if (klass.isPrimitive()) { 323 return PrimitiveArrays.toPrimitiveArray(list, klass); 324 } else { 325 return list.toArray((Object[]) Array.newInstance(klass, list.size())); 326 } 327 } 328 return list; 329 } else { 330 ComplexType ctype = (ComplexType) type; 331 if (TypeConstants.isContentType(ctype)) { 332 String mimeType = element.elementText(ExportConstants.BLOB_MIME_TYPE); 333 String encoding = element.elementText(ExportConstants.BLOB_ENCODING); 334 String content = element.elementTextTrim(ExportConstants.BLOB_DATA); 335 String filename = element.elementTextTrim(ExportConstants.BLOB_FILENAME); 336 if ((content == null || content.length() == 0) && (mimeType == null || mimeType.length() == 0)) { 337 return null; // remove blob 338 } 339 Blob blob = null; 340 if (xdoc.hasExternalBlobs()) { 341 blob = xdoc.getBlob(content); 342 } 343 if (blob == null) { // maybe the blob is embedded in Base64 344 // encoded data 345 byte[] bytes = Base64.decode(content); 346 blob = Blobs.createBlob(bytes); 347 } 348 blob.setMimeType(mimeType); 349 blob.setEncoding(encoding); 350 blob.setFilename(filename); 351 return blob; 352 } else { // a complex type 353 Map<String, Object> map = new HashMap<String, Object>(); 354 Iterator<Element> it = element.elementIterator(); 355 while (it.hasNext()) { 356 Element el = it.next(); 357 String name = el.getName(); 358 Object value = getElementData(xdoc, el, ctype.getField(el.getName()).getType()); 359 map.put(name, value); 360 } 361 return map; 362 } 363 } 364 } 365 366}