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