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 */ 012package org.nuxeo.ecm.automation.core.util; 013 014import java.io.IOException; 015import java.io.InputStream; 016import java.io.Serializable; 017import java.util.ArrayList; 018import java.util.Calendar; 019import java.util.HashMap; 020import java.util.Map; 021 022import org.nuxeo.common.utils.StringUtils; 023import org.nuxeo.ecm.core.api.Blob; 024import org.nuxeo.ecm.core.api.CoreSession; 025import org.nuxeo.ecm.core.api.DocumentModel; 026import org.nuxeo.ecm.core.api.NuxeoException; 027import org.nuxeo.ecm.core.api.PropertyException; 028import org.nuxeo.ecm.core.api.model.Property; 029import org.nuxeo.ecm.core.api.model.impl.ListProperty; 030import org.nuxeo.ecm.core.api.security.ACE; 031import org.nuxeo.ecm.core.api.security.ACL; 032import org.nuxeo.ecm.core.api.security.impl.ACLImpl; 033import org.nuxeo.ecm.core.api.security.impl.ACPImpl; 034import org.nuxeo.ecm.core.schema.types.ComplexType; 035import org.nuxeo.ecm.core.schema.types.ListType; 036import org.nuxeo.ecm.core.schema.types.SimpleType; 037import org.nuxeo.ecm.core.schema.types.Type; 038import org.nuxeo.ecm.core.schema.types.primitives.BinaryType; 039import org.nuxeo.ecm.core.schema.types.primitives.BooleanType; 040import org.nuxeo.ecm.core.schema.types.primitives.DateType; 041import org.nuxeo.ecm.core.schema.types.primitives.DoubleType; 042import org.nuxeo.ecm.core.schema.types.primitives.IntegerType; 043import org.nuxeo.ecm.core.schema.types.primitives.LongType; 044import org.nuxeo.ecm.core.schema.types.primitives.StringType; 045 046/** 047 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a> 048 */ 049public class DocumentHelper { 050 051 private DocumentHelper() { 052 } 053 054 /** 055 * Saves the document and clear context data to avoid incrementing version in next operations if not needed. 056 */ 057 public static DocumentModel saveDocument(CoreSession session, DocumentModel doc) { 058 doc = session.saveDocument(doc); 059 return session.getDocument(doc.getRef()); 060 } 061 062 /** 063 * Removes a property from a document given the xpath. If the xpath points to a list property the list will be 064 * cleared. If the path points to a blob in a list the property is removed from the list. Otherwise the xpath should 065 * point to a non list property that will be removed. 066 */ 067 public static void removeProperty(DocumentModel doc, String xpath) { 068 Property p = doc.getProperty(xpath); 069 if (p instanceof ListProperty) { 070 ((ListProperty) p).clear(); 071 } else { 072 Property pp = p.getParent(); 073 if (pp != null && pp.isList()) { // remove list entry 074 ((ListProperty) pp).remove(p); 075 } else { 076 p.remove(); 077 } 078 } 079 } 080 081 /** 082 * Given a document property, updates its value with the given blob. The property can be a blob list or a blob. If a 083 * blob list the blob is appended to the list, if a blob then it will be set as the property value. Both blob list 084 * formats are supported: the file list (blob holder list) and simple blob list. 085 */ 086 public static void addBlob(Property p, Blob blob) throws PropertyException { 087 if (p.isList()) { 088 // detect if a list of simple blobs or a list of files (blob 089 // holder) 090 Type ft = ((ListProperty) p).getType().getFieldType(); 091 if (ft.isComplexType() && ((ComplexType) ft).getFieldsCount() == 2) { 092 p.addValue(createBlobHolderMap(blob)); 093 } else { 094 p.addValue(blob); 095 } 096 } else { 097 p.setValue(blob); 098 } 099 } 100 101 public static HashMap<String, Serializable> createBlobHolderMap(Blob blob) { 102 HashMap<String, Serializable> map = new HashMap<String, Serializable>(); 103 map.put("file", (Serializable) blob); 104 map.put("filename", blob.getFilename()); 105 return map; 106 } 107 108 public static void setProperties(CoreSession session, DocumentModel doc, Properties properties) 109 throws IOException, PropertyException { 110 if (properties instanceof DataModelProperties) { 111 DataModelProperties dataModelProperties = (DataModelProperties) properties; 112 for (Map.Entry<String, Serializable> entry : dataModelProperties.getMap().entrySet()) { 113 doc.setPropertyValue(entry.getKey(), entry.getValue()); 114 } 115 } else { 116 for (Map.Entry<String, String> entry : properties.entrySet()) { 117 String key = entry.getKey(); 118 String value = entry.getValue(); 119 setProperty(session, doc, key, value); 120 } 121 } 122 } 123 124 /** 125 * Sets the properties given as a map of xpath:value to the given document. There is one special property: ecm:acl 126 * that can be used to set the local acl. The format of this property value is: [string username]:[string 127 * permission]:[boolean grant], [string username]:[string permission]:[boolean grant], ... TODO list properties are 128 * not yet supported 129 */ 130 public static void setProperties(CoreSession session, DocumentModel doc, Map<String, String> values) 131 throws IOException { 132 for (Map.Entry<String, String> entry : values.entrySet()) { 133 String key = entry.getKey(); 134 String value = entry.getValue(); 135 setProperty(session, doc, key, value); 136 } 137 } 138 139 public static void setProperty(CoreSession session, DocumentModel doc, String key, String value) 140 throws IOException { 141 setProperty(session, doc, key, value, false); 142 } 143 144 protected static void setLocalAcl(CoreSession session, DocumentModel doc, String value) { 145 ACPImpl acp = new ACPImpl(); 146 ACLImpl acl = new ACLImpl(ACL.LOCAL_ACL); 147 acp.addACL(acl); 148 String[] entries = StringUtils.split(value, ',', true); 149 if (entries.length == 0) { 150 return; 151 } 152 for (String entry : entries) { 153 String[] ace = StringUtils.split(entry, ':', true); 154 acl.add(new ACE(ace[0], ace[1], Boolean.parseBoolean(ace[2]))); 155 } 156 session.setACP(doc.getRef(), acp, false); 157 } 158 159 /** 160 * Read an encoded string list as a comma separated list. To use comma inside list element values you need to escape 161 * them using '\'. If the given type is different from {@link StringType#ID} then array elements will be converted 162 * to the actual type. 163 * 164 * @param value 165 * @param type 166 * @return 167 */ 168 public static Object readStringList(String value, SimpleType type) { 169 if (!type.isPrimitive()) { 170 return readStringList(value, type.getPrimitiveType()); 171 } 172 String[] ar = readStringList(value); 173 if (ar == null) { 174 return null; 175 } 176 if (StringType.INSTANCE == type) { 177 return ar; 178 } else if (DateType.INSTANCE == type) { 179 Calendar[] r = new Calendar[ar.length]; 180 for (int i = 0; i < r.length; i++) { 181 r[i] = (Calendar) type.decode(ar[i]); 182 } 183 return r; 184 } else if (LongType.INSTANCE == type) { 185 Long[] r = new Long[ar.length]; 186 for (int i = 0; i < r.length; i++) { 187 r[i] = (Long) type.decode(ar[i]); 188 } 189 return r; 190 } else if (IntegerType.INSTANCE == type) { 191 Integer[] r = new Integer[ar.length]; 192 for (int i = 0; i < r.length; i++) { 193 r[i] = (Integer) type.decode(ar[i]); 194 } 195 return r; 196 } else if (DoubleType.INSTANCE == type) { 197 Double[] r = new Double[ar.length]; 198 for (int i = 0; i < r.length; i++) { 199 r[i] = (Double) type.decode(ar[i]); 200 } 201 return r; 202 } else if (BooleanType.INSTANCE == type) { 203 Boolean[] r = new Boolean[ar.length]; 204 for (int i = 0; i < r.length; i++) { 205 r[i] = (Boolean) type.decode(ar[i]); 206 } 207 return r; 208 } else if (BinaryType.INSTANCE == type) { 209 InputStream[] r = new InputStream[ar.length]; 210 for (int i = 0; i < r.length; i++) { 211 r[i] = (InputStream) type.decode(ar[i]); 212 } 213 return r; 214 } 215 throw new IllegalArgumentException( 216 "Unsupported type when updating document properties from string representation: " + type); 217 } 218 219 /** 220 * Read an encoded string list as a comma separated list. To use comma inside list element values you need to escape 221 * them using '\'. 222 * 223 * @param value 224 * @return 225 */ 226 public static String[] readStringList(String value) { 227 if (value == null) { 228 return null; 229 } 230 if (value.length() == 0) { 231 return new String[0]; 232 } 233 ArrayList<String> result = new ArrayList<String>(); 234 char[] chars = value.toCharArray(); 235 StringBuilder buf = new StringBuilder(); 236 boolean esc = false; 237 for (int i = 0; i < chars.length; i++) { 238 char c = chars[i]; 239 if (c == '\\') { 240 if (esc) { 241 buf.append('\\'); 242 esc = false; 243 } else { 244 esc = true; 245 } 246 } else if (c == ',') { 247 if (esc) { 248 buf.append(','); 249 esc = false; 250 } else { 251 result.add(buf.toString()); 252 buf = new StringBuilder(); 253 } 254 } else { 255 buf.append(c); 256 } 257 } 258 result.add(buf.toString()); 259 return result.toArray(new String[result.size()]); 260 } 261 262 /** 263 * Sets the properties of a document based on their JSON representation (especially for scalar lists). 264 * 265 * @param session 266 * @param doc 267 * @param properties 268 * @throws IOException 269 * @since 5.9.2 270 */ 271 public static void setJSONProperties(CoreSession session, DocumentModel doc, Properties properties) 272 throws IOException { 273 274 for (Map.Entry<String, String> entry : properties.entrySet()) { 275 String key = entry.getKey(); 276 String value = entry.getValue(); 277 setProperty(session, doc, key, value, true); 278 } 279 } 280 281 /** 282 * @param session 283 * @param doc 284 * @param key 285 * @param value 286 * @param decodeStringListAsJSON 287 * @throws IOException 288 * @since 5.9.2 289 */ 290 public static void setProperty(CoreSession session, DocumentModel doc, String key, String value, 291 boolean decodeStringListAsJSON) throws IOException { 292 if ("ecm:acl".equals(key)) { 293 setLocalAcl(session, doc, value); 294 } 295 Property p = doc.getProperty(key); 296 if (value == null || value.length() == 0) { 297 p.setValue(null); 298 return; 299 } 300 Type type = p.getField().getType(); 301 if (!type.isSimpleType()) { 302 if (type.isListType()) { 303 ListType ltype = (ListType) type; 304 if (ltype.isScalarList() && !decodeStringListAsJSON) { 305 p.setValue(readStringList(value, (SimpleType) ltype.getFieldType())); 306 return; 307 } else { 308 Object val = ComplexTypeJSONDecoder.decodeList(ltype, value); 309 p.setValue(val); 310 return; 311 } 312 } else if (type.isComplexType()) { 313 Object val = ComplexTypeJSONDecoder.decode((ComplexType) type, value); 314 p.setValue(val); 315 return; 316 } 317 throw new NuxeoException("Property type is not supported by this operation"); 318 } else { 319 p.setValue(((SimpleType) type).getPrimitiveType().decode(value)); 320 } 321 } 322 323}