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 doc = session.createDocument(doc); 155 156 // load into the document the system properties, document needs to exist 157 loadSystemInfo(doc, xdoc.getDocument()); 158 159 unsavedDocuments += 1; 160 saveIfNeeded(); 161 162 return doc; 163 } 164 165 /** 166 * Updates an existing document. 167 */ 168 protected DocumentModel updateDocument(ExportedDocument xdoc, DocumentModel doc) { 169 // load schemas data 170 loadSchemas(xdoc, doc, xdoc.getDocument()); 171 172 loadFacetsInfo(doc, xdoc.getDocument()); 173 174 doc = session.saveDocument(doc); 175 176 unsavedDocuments += 1; 177 saveIfNeeded(); 178 179 return doc; 180 } 181 182 public int getSaveInterval() { 183 return saveInterval; 184 } 185 186 public void setSaveInterval(int saveInterval) { 187 this.saveInterval = saveInterval; 188 } 189 190 @SuppressWarnings("unchecked") 191 protected boolean loadFacetsInfo(DocumentModel docModel, Document doc) { 192 boolean added = false; 193 Element system = doc.getRootElement().element(ExportConstants.SYSTEM_TAG); 194 if (system == null) { 195 return false; 196 } 197 198 Iterator<Element> facets = system.elementIterator(ExportConstants.FACET_TAG); 199 while (facets.hasNext()) { 200 Element element = facets.next(); 201 String facet = element.getTextTrim(); 202 if (!docModel.hasFacet(facet)) { 203 docModel.addFacet(facet); 204 added = true; 205 } 206 } 207 208 return added; 209 } 210 211 @SuppressWarnings("unchecked") 212 protected void loadSystemInfo(DocumentModel docModel, Document doc) { 213 Element system = doc.getRootElement().element(ExportConstants.SYSTEM_TAG); 214 215 Element accessControl = system.element(ExportConstants.ACCESS_CONTROL_TAG); 216 if (accessControl == null) { 217 return; 218 } 219 Iterator<Element> it = accessControl.elementIterator(ExportConstants.ACL_TAG); 220 while (it.hasNext()) { 221 Element element = it.next(); 222 // import only the local acl 223 if (ACL.LOCAL_ACL.equals(element.attributeValue(ExportConstants.NAME_ATTR))) { 224 // this is the local ACL - import it 225 List<Element> entries = element.elements(); 226 int size = entries.size(); 227 if (size > 0) { 228 ACP acp = new ACPImpl(); 229 ACL acl = new ACLImpl(ACL.LOCAL_ACL); 230 acp.addACL(acl); 231 for (int i = 0; i < size; i++) { 232 Element el = entries.get(i); 233 String username = el.attributeValue(ExportConstants.PRINCIPAL_ATTR); 234 String permission = el.attributeValue(ExportConstants.PERMISSION_ATTR); 235 String grant = el.attributeValue(ExportConstants.GRANT_ATTR); 236 String creator = el.attributeValue(ExportConstants.CREATOR_ATTR); 237 String beginStr = el.attributeValue(ExportConstants.BEGIN_ATTR); 238 Calendar begin = null; 239 if (beginStr != null) { 240 Date date = DateParser.parseW3CDateTime(beginStr); 241 begin = new GregorianCalendar(); 242 begin.setTimeInMillis(date.getTime()); 243 } 244 String endStr = el.attributeValue(ExportConstants.END_ATTR); 245 Calendar end = null; 246 if (endStr != null) { 247 Date date = DateParser.parseW3CDateTime(endStr); 248 end = new GregorianCalendar(); 249 end.setTimeInMillis(date.getTime()); 250 } 251 ACE ace = ACE.builder(username, permission) 252 .isGranted(Boolean.parseBoolean(grant)) 253 .creator(creator) 254 .begin(begin) 255 .end(end) 256 .build(); 257 acl.add(ace); 258 } 259 acp.addACL(acl); 260 session.setACP(docModel.getRef(), acp, false); 261 } 262 } 263 } 264 } 265 266 @SuppressWarnings("unchecked") 267 protected void loadSchemas(ExportedDocument xdoc, DocumentModel docModel, Document doc) { 268 SchemaManager schemaMgr = Framework.getLocalService(SchemaManager.class); 269 Iterator<Element> it = doc.getRootElement().elementIterator(ExportConstants.SCHEMA_TAG); 270 while (it.hasNext()) { 271 Element element = it.next(); 272 String schemaName = element.attributeValue(ExportConstants.NAME_ATTR); 273 Schema schema = schemaMgr.getSchema(schemaName); 274 if (schema == null) { 275 throw new NuxeoException("Schema not found: " + schemaName); 276 } 277 loadSchema(xdoc, schema, docModel, element); 278 } 279 } 280 281 @SuppressWarnings("unchecked") 282 protected static void loadSchema(ExportedDocument xdoc, Schema schema, DocumentModel doc, Element schemaElement) { 283 String schemaName = schemaElement.attributeValue(ExportConstants.NAME_ATTR); 284 Map<String, Object> data = new HashMap<String, Object>(); 285 Iterator<Element> it = schemaElement.elementIterator(); 286 while (it.hasNext()) { 287 Element element = it.next(); 288 String name = element.getName(); 289 Field field = schema.getField(name); 290 if (field == null) { 291 throw new NuxeoException("Invalid input document. No such property was found " + name + " in schema " 292 + schemaName); 293 } 294 Object value = getElementData(xdoc, element, field.getType()); 295 data.put(name, value); 296 } 297 doc.setProperties(schemaName, data); 298 } 299 300 protected static Class getFieldClass(Type fieldType) { 301 Class klass = JavaTypes.getClass(fieldType); 302 // for enumerated SimpleTypes we may need to lookup on the supertype 303 // we do the recursion here and not in JavaTypes to avoid potential impacts 304 if (klass == null && fieldType.getSuperType() != null) { 305 return getFieldClass(fieldType.getSuperType()); 306 } 307 return klass; 308 } 309 310 @SuppressWarnings("unchecked") 311 private static Object getElementData(ExportedDocument xdoc, Element element, Type type) { 312 // empty xml tag must be null value (not empty string) 313 if (!element.hasContent()) { 314 return null; 315 } 316 if (type.isSimpleType()) { 317 return type.decode(element.getText()); 318 } else if (type.isListType()) { 319 ListType ltype = (ListType) type; 320 List<Object> list = new ArrayList<Object>(); 321 Iterator<Element> it = element.elementIterator(); 322 while (it.hasNext()) { 323 Element el = it.next(); 324 list.add(getElementData(xdoc, el, ltype.getFieldType())); 325 } 326 Type ftype = ltype.getFieldType(); 327 if (ftype.isSimpleType()) { // these are stored as arrays 328 Class klass = getFieldClass(ftype); 329 if (klass.isPrimitive()) { 330 return PrimitiveArrays.toPrimitiveArray(list, klass); 331 } else { 332 return list.toArray((Object[]) Array.newInstance(klass, list.size())); 333 } 334 } 335 return list; 336 } else { 337 ComplexType ctype = (ComplexType) type; 338 if (TypeConstants.isContentType(ctype)) { 339 String mimeType = element.elementText(ExportConstants.BLOB_MIME_TYPE); 340 String encoding = element.elementText(ExportConstants.BLOB_ENCODING); 341 String content = element.elementTextTrim(ExportConstants.BLOB_DATA); 342 String filename = element.elementTextTrim(ExportConstants.BLOB_FILENAME); 343 if ((content == null || content.length() == 0) && (mimeType == null || mimeType.length() == 0)) { 344 return null; // remove blob 345 } 346 Blob blob = null; 347 if (xdoc.hasExternalBlobs()) { 348 blob = xdoc.getBlob(content); 349 } 350 if (blob == null) { // maybe the blob is embedded in Base64 351 // encoded data 352 byte[] bytes = Base64.decode(content); 353 blob = Blobs.createBlob(bytes); 354 } 355 blob.setMimeType(mimeType); 356 blob.setEncoding(encoding); 357 blob.setFilename(filename); 358 return blob; 359 } else { // a complex type 360 Map<String, Object> map = new HashMap<String, Object>(); 361 Iterator<Element> it = element.elementIterator(); 362 while (it.hasNext()) { 363 Element el = it.next(); 364 String name = el.getName(); 365 Object value = getElementData(xdoc, el, ctype.getField(el.getName()).getType()); 366 map.put(name, value); 367 } 368 return map; 369 } 370 } 371 } 372 373}