001/* 002 * (C) Copyright 2006-2016 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:[email protected]">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() == 1) { 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<>(); 110 map.put("file", (Serializable) blob); 111 return map; 112 } 113 114 public static void setProperties(CoreSession session, DocumentModel doc, Properties properties) 115 throws IOException, PropertyException { 116 if (properties instanceof DataModelProperties) { 117 DataModelProperties dataModelProperties = (DataModelProperties) properties; 118 for (Map.Entry<String, Serializable> entry : dataModelProperties.getMap().entrySet()) { 119 doc.setPropertyValue(entry.getKey(), entry.getValue()); 120 } 121 } 122 for (Map.Entry<String, String> entry : properties.entrySet()) { 123 String key = entry.getKey(); 124 String value = entry.getValue(); 125 setProperty(session, doc, key, value); 126 } 127 } 128 129 /** 130 * Sets the properties given as a map of xpath:value to the given document. There is one special property: ecm:acl 131 * that can be used to set the local acl. The format of this property value is: [string username]:[string 132 * permission]:[boolean grant], [string username]:[string permission]:[boolean grant], ... TODO list properties are 133 * not yet supported 134 */ 135 public static void setProperties(CoreSession session, DocumentModel doc, Map<String, String> values) 136 throws IOException { 137 for (Map.Entry<String, String> entry : values.entrySet()) { 138 String key = entry.getKey(); 139 String value = entry.getValue(); 140 setProperty(session, doc, key, value); 141 } 142 } 143 144 public static void setProperty(CoreSession session, DocumentModel doc, String key, String value) 145 throws IOException { 146 setProperty(session, doc, key, value, false); 147 } 148 149 protected static void setLocalAcl(CoreSession session, DocumentModel doc, String value) { 150 ACPImpl acp = new ACPImpl(); 151 ACLImpl acl = new ACLImpl(ACL.LOCAL_ACL); 152 acp.addACL(acl); 153 String[] entries = StringUtils.split(value, ',', true); 154 if (entries.length == 0) { 155 return; 156 } 157 for (String entry : entries) { 158 String[] ace = StringUtils.split(entry, ':', true); 159 acl.add(new ACE(ace[0], ace[1], Boolean.parseBoolean(ace[2]))); 160 } 161 session.setACP(doc.getRef(), acp, false); 162 } 163 164 /** 165 * Read an encoded string list as a comma separated list. To use comma inside list element values you need to escape 166 * them using '\'. If the given type is different from {@link StringType#ID} then array elements will be converted 167 * to the actual type. 168 */ 169 public static Object readStringList(String value, SimpleType type) { 170 if (!type.isPrimitive()) { 171 return readStringList(value, type.getPrimitiveType()); 172 } 173 String[] ar = readStringList(value); 174 if (ar == null) { 175 return null; 176 } 177 if (StringType.INSTANCE == type) { 178 return ar; 179 } else if (DateType.INSTANCE == type) { 180 Calendar[] r = new Calendar[ar.length]; 181 for (int i = 0; i < r.length; i++) { 182 r[i] = (Calendar) type.decode(ar[i]); 183 } 184 return r; 185 } else if (LongType.INSTANCE == type) { 186 Long[] r = new Long[ar.length]; 187 for (int i = 0; i < r.length; i++) { 188 r[i] = (Long) type.decode(ar[i]); 189 } 190 return r; 191 } else if (IntegerType.INSTANCE == type) { 192 Integer[] r = new Integer[ar.length]; 193 for (int i = 0; i < r.length; i++) { 194 r[i] = (Integer) type.decode(ar[i]); 195 } 196 return r; 197 } else if (DoubleType.INSTANCE == type) { 198 Double[] r = new Double[ar.length]; 199 for (int i = 0; i < r.length; i++) { 200 r[i] = (Double) type.decode(ar[i]); 201 } 202 return r; 203 } else if (BooleanType.INSTANCE == type) { 204 Boolean[] r = new Boolean[ar.length]; 205 for (int i = 0; i < r.length; i++) { 206 r[i] = (Boolean) type.decode(ar[i]); 207 } 208 return r; 209 } else if (BinaryType.INSTANCE == type) { 210 InputStream[] r = new InputStream[ar.length]; 211 for (int i = 0; i < r.length; i++) { 212 r[i] = (InputStream) type.decode(ar[i]); 213 } 214 return r; 215 } 216 throw new IllegalArgumentException( 217 "Unsupported type when updating document properties from string representation: " + type); 218 } 219 220 /** 221 * Read an encoded string list as a comma separated list. To use comma inside list element values you need to escape 222 * them using '\'. 223 */ 224 public static String[] readStringList(String value) { 225 if (value == null) { 226 return null; 227 } 228 if (value.length() == 0) { 229 return new String[0]; 230 } 231 ArrayList<String> result = new ArrayList<>(); 232 char[] chars = value.toCharArray(); 233 StringBuilder buf = new StringBuilder(); 234 boolean esc = false; 235 for (char c : chars) { 236 if (c == '\\') { 237 if (esc) { 238 buf.append('\\'); 239 esc = false; 240 } else { 241 esc = true; 242 } 243 } else if (c == ',') { 244 if (esc) { 245 buf.append(','); 246 esc = false; 247 } else { 248 result.add(buf.toString()); 249 buf = new StringBuilder(); 250 } 251 } else { 252 buf.append(c); 253 } 254 } 255 result.add(buf.toString()); 256 return result.toArray(new String[result.size()]); 257 } 258 259 /** 260 * Sets the properties of a document based on their JSON representation (especially for scalar lists). 261 * 262 * @since 5.9.2 263 */ 264 public static void setJSONProperties(CoreSession session, DocumentModel doc, Properties properties) 265 throws IOException { 266 267 for (Map.Entry<String, String> entry : properties.entrySet()) { 268 String key = entry.getKey(); 269 String value = entry.getValue(); 270 setProperty(session, doc, key, value, true); 271 } 272 } 273 274 /** 275 * @since 5.9.2 276 */ 277 public static void setProperty(CoreSession session, DocumentModel doc, String key, String value, 278 boolean decodeStringListAsJSON) throws IOException { 279 if ("ecm:acl".equals(key)) { 280 setLocalAcl(session, doc, value); 281 } 282 Property p = doc.getProperty(key); 283 if (value == null || value.length() == 0) { 284 p.setValue(null); 285 return; 286 } 287 Type type = p.getField().getType(); 288 if (!type.isSimpleType()) { 289 if (type.isListType()) { 290 ListType ltype = (ListType) type; 291 if (ltype.isScalarList() && !decodeStringListAsJSON) { 292 p.setValue(readStringList(value, (SimpleType) ltype.getFieldType())); 293 return; 294 } else { 295 Object val = ComplexTypeJSONDecoder.decodeList(ltype, value); 296 p.setValue(val); 297 return; 298 } 299 } else if (type.isComplexType()) { 300 Object val = ComplexTypeJSONDecoder.decode((ComplexType) type, value); 301 p.setValue(val); 302 return; 303 } 304 throw new NuxeoException("Property type is not supported by this operation"); 305 } else { 306 p.setValue(((SimpleType) type).getPrimitiveType().decode(value)); 307 } 308 } 309 310}