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