001/* 002 * (C) Copyright 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.automation.scripting.internals; 020 021import java.io.Serializable; 022import java.util.ArrayList; 023import java.util.Arrays; 024import java.util.Collection; 025import java.util.Collections; 026import java.util.HashMap; 027import java.util.List; 028import java.util.Map; 029import java.util.Set; 030import java.util.stream.Collectors; 031import java.util.stream.Stream; 032 033import org.nuxeo.ecm.automation.core.util.BlobList; 034import org.nuxeo.ecm.automation.core.util.DataModelProperties; 035import org.nuxeo.ecm.automation.core.util.Properties; 036import org.nuxeo.ecm.core.api.Blob; 037import org.nuxeo.ecm.core.api.CoreSession; 038import org.nuxeo.ecm.core.api.DocumentModel; 039import org.nuxeo.ecm.core.api.DocumentModelList; 040import org.nuxeo.ecm.core.api.DocumentRef; 041import org.nuxeo.ecm.core.api.PathRef; 042import org.nuxeo.ecm.core.api.PropertyException; 043import org.nuxeo.ecm.core.api.impl.DocumentModelListImpl; 044import org.nuxeo.ecm.core.api.model.Property; 045import org.nuxeo.ecm.core.schema.DocumentType; 046 047import jdk.nashorn.api.scripting.ScriptObjectMirror; 048import jdk.nashorn.internal.objects.NativeArray; 049 050/** 051 * Wrap a {@link DocumentModel} to expose in a pretty way more information to automation scripts. 052 * 053 * @since 8.4 054 */ 055public class DocumentScriptingWrapper extends HashMap<String, Object> { 056 057 private static final long serialVersionUID = 1L; 058 059 protected final AutomationMapper mapper; 060 061 protected final DocumentModel doc; 062 063 public static Object wrap(Object object, AutomationMapper mapper) { 064 if (object == null) { 065 return null; 066 } 067 if (object instanceof DocumentModel) { 068 return new DocumentScriptingWrapper(mapper, (DocumentModel) object); 069 } else if (object instanceof DocumentModelList) { 070 List<DocumentScriptingWrapper> docs = new ArrayList<>(); 071 for (DocumentModel doc : (DocumentModelList) object) { 072 docs.add(new DocumentScriptingWrapper(mapper, doc)); 073 } 074 return docs; 075 } else if (object instanceof Map<?, ?>) { 076 @SuppressWarnings("unchecked") 077 Map<String, Object> m = (Map<String, Object>) object; 078 return wrap(m, mapper); 079 } 080 return object; 081 } 082 083 public static Map<String, Object> wrap(Map<String, Object> source, AutomationMapper mapper) { 084 return source.entrySet().stream() 085 .collect(Collectors.toMap(Map.Entry::getKey, e -> wrap(e.getValue(), mapper))); 086 } 087 088 public static Object unwrap(Object object) { 089 // First unwrap object if it's a nashorn object 090 Object result = object; 091 if (result instanceof ScriptObjectMirror) { 092 result = ScriptObjectMirrors.unwrap((ScriptObjectMirror) result); 093 } 094 // TODO: not sure if this code is used, but we shouldn't use NativeArray as it's an internal class of nashorn 095 if (result instanceof NativeArray) { 096 result = Arrays.asList(((NativeArray) result).asObjectArray()); 097 } 098 // Second unwrap object 099 if (result instanceof DocumentScriptingWrapper) { 100 result = ((DocumentScriptingWrapper) result).getDoc(); 101 } else if (result instanceof List<?>) { 102 List<?> l = (List<?>) result; 103 // Several possible cases here: 104 // - l is of type DocumentModelList or BlobList -> already in right type 105 // - l is a list of DocumentScriptingWrapper -> elements need to be unwrapped into a DocumentModelList 106 // - l is a list of DocumentWrapper -> l needs to be converted to DocumentModelList 107 // - l is a list of Blob -> l needs to be converted to BlobList 108 // - l is a list -> do nothing 109 if (l.size() > 0 && !(result instanceof DocumentModelList || result instanceof BlobList)) { 110 Object first = l.get(0); 111 if (first instanceof DocumentModel) { 112 result = l.stream().map(DocumentModel.class::cast) 113 .collect(Collectors.toCollection(DocumentModelListImpl::new)); 114 } else if (first instanceof Blob) { 115 result = l.stream().map(Blob.class::cast).collect(Collectors.toCollection(BlobList::new)); 116 } else if (first instanceof DocumentScriptingWrapper) { 117 result = l.stream().map(DocumentScriptingWrapper.class::cast).map(DocumentScriptingWrapper::getDoc) 118 .collect(Collectors.toCollection(DocumentModelListImpl::new)); 119 } 120 } 121 } else if (result instanceof Map<?, ?>) { 122 @SuppressWarnings("unchecked") 123 final Map<String, Object> map = (Map<String, Object>) result; 124 result = computeProperties(map); 125 } 126 return result; 127 } 128 129 protected static Properties computeProperties(Map<?, ?> result) { 130 DataModelProperties props = new DataModelProperties(); 131 for (Entry<?, ?> entry : result.entrySet()) { 132 props.getMap().put(entry.getKey().toString(), (Serializable) entry.getValue()); 133 } 134 return props; 135 } 136 137 public static Map<String, Object> unwrap(Map<String, Object> source) { 138 return source.entrySet().stream().filter(e -> e.getValue() != null) 139 .collect(Collectors.toMap(Map.Entry::getKey, e -> unwrap(e.getValue()))); 140 } 141 142 public DocumentScriptingWrapper(AutomationMapper mapper, DocumentModel doc) { 143 this.mapper = mapper; 144 this.doc = doc; 145 } 146 147 public DocumentModel getDoc() { 148 return doc; 149 } 150 151 public CoreSession getSession() { 152 return mapper.ctx.getCoreSession(); 153 } 154 155 public DocumentScriptingWrapper getParent() { 156 DocumentModel parent = getSession().getParentDocument(doc.getRef()); 157 return parent != null ? new DocumentScriptingWrapper(mapper, parent) : null; 158 } 159 160 public DocumentScriptingWrapper getParent(String type) { 161 DocumentModel parent = getSession().getParentDocument(doc.getRef()); 162 while (parent != null && !type.equals(parent.getType())) { 163 parent = getSession().getParentDocument(parent.getRef()); 164 } 165 if (parent == null) { 166 return null; 167 } 168 return new DocumentScriptingWrapper(mapper, parent); 169 } 170 171 public DocumentScriptingWrapper getWorkspace() { 172 return getParent("Workspace"); 173 } 174 175 public DocumentScriptingWrapper getDomain() { 176 return getParent("Domain"); 177 } 178 179 public String getTitle() { 180 return doc.getTitle(); 181 } 182 183 public String getPath() { 184 return doc.getPathAsString(); 185 } 186 187 public String resolvePath(String relative) { 188 return doc.getPath().append(relative).toString(); 189 } 190 191 /** 192 * @return the document ref 193 */ 194 public DocumentRef getRef() { 195 return doc.getRef(); 196 } 197 198 public DocumentRef resolvePathAsRef(String relative) { 199 return new PathRef(doc.getPath().append(relative).toString()); 200 } 201 202 public String getDescription() { 203 return (String) doc.getPropertyValue("dc:description"); 204 } 205 206 public boolean hasFacet(String facet) { 207 return doc.hasFacet(facet); 208 } 209 210 public boolean hasSchema(String schema) { 211 return doc.hasSchema(schema); 212 } 213 214 public boolean addFacet(String facet) { 215 return doc.addFacet(facet); 216 } 217 218 public boolean removeFacet(String facet) { 219 return doc.removeFacet(facet); 220 } 221 222 public String getType() { 223 return doc.getType(); 224 } 225 226 public DocumentType getDocumentType() { 227 return doc.getDocumentType(); 228 } 229 230 public String getLifeCycle() { 231 return doc.getCurrentLifeCycleState(); 232 } 233 234 public boolean isLocked() { 235 return doc.isLocked(); 236 } 237 238 public boolean isFolder() { 239 return doc.isFolder(); 240 } 241 242 public boolean isImmutable() { 243 return doc.isImmutable(); 244 } 245 246 public boolean isProxy() { 247 return doc.isProxy(); 248 } 249 250 public boolean isVersion() { 251 return doc.isVersion(); 252 } 253 254 public boolean isDownloadable() { 255 return doc.isDownloadable(); 256 } 257 258 public boolean isVersionable() { 259 return doc.isVersionable(); 260 } 261 262 public String getId() { 263 return doc.getId(); 264 } 265 266 public String getName() { 267 return doc.getName(); 268 } 269 270 public String[] getSchemas() { 271 return doc.getSchemas(); 272 } 273 274 public Set<String> getFacets() { 275 return doc.getFacets(); 276 } 277 278 public Serializable getProperty(String key) { 279 return doc.getPropertyValue(key); 280 } 281 282 /** 283 * Alias for #getProperty. 284 */ 285 public Serializable getPropertyValue(String key) { 286 return doc.getPropertyValue(key); 287 } 288 289 public void setProperty(String key, Serializable value) { 290 doc.setPropertyValue(key, value); 291 } 292 293 /** 294 * Alias for #setProperty. 295 */ 296 public void setPropertyValue(String key, Serializable value) { 297 doc.setPropertyValue(key, value); 298 } 299 300 /** 301 * Used by nashorn for native javascript array/date. 302 */ 303 public void setPropertyValue(String key, ScriptObjectMirror value) { 304 doc.setPropertyValue(key, (Serializable) ScriptObjectMirrors.unwrap(value)); 305 } 306 307 public String getVersionLabel() { 308 return doc.getVersionLabel(); 309 } 310 311 /** property map implementation */ 312 313 @Override 314 public boolean containsKey(Object key) { 315 try { 316 doc.getProperty(key.toString()); 317 return true; 318 } catch (PropertyException e) { 319 return false; 320 } 321 } 322 323 /** 324 * The behavior of this method was changed -> it is checking if an xpath has a value attached. 325 */ 326 @Override 327 public boolean containsValue(Object value) { 328 try { 329 return doc.getProperty(value.toString()).getValue() != null; 330 } catch (PropertyException e) { 331 return false; 332 } 333 } 334 335 @Override 336 public Serializable get(Object key) { 337 try { 338 return doc.getProperty(key.toString()).getValue(); 339 } catch (PropertyException e) { 340 return null; 341 } 342 } 343 344 @Override 345 public boolean isEmpty() { 346 return false; 347 } 348 349 @Override 350 public int size() { 351 return Stream.of(doc.getParts()).collect(Collectors.summingInt(part -> part.size())); 352 } 353 354 @Override 355 public Set<String> keySet() { 356 return Collections.unmodifiableSet(Stream.of(doc.getSchemas()) 357 .map(name -> doc.getProperties(name).keySet().stream()).flatMap(s -> s).collect(Collectors.toSet())); 358 } 359 360 @Override 361 public Collection<Object> values() { 362 return Collections.unmodifiableCollection(Stream.of(doc.getSchemas()) 363 .map(name -> doc.getProperties(name).values().stream()).flatMap(s -> s).collect(Collectors.toSet())); 364 } 365 366 @Override 367 public Set<Entry<String, Object>> entrySet() { 368 return Collections.unmodifiableSet(Stream.of(doc.getSchemas()) 369 .flatMap(name -> doc.getProperties(name).entrySet().stream()).collect(Collectors.toSet())); 370 } 371 372 /** 373 * As we need to handle {@link ScriptObjectMirror} for array type from nashorn. 374 */ 375 @Override 376 public Object put(String key, Object value) { 377 if (value instanceof ScriptObjectMirror) { 378 return put(key, (Serializable) ScriptObjectMirrors.unwrap((ScriptObjectMirror) value)); 379 } 380 return put(key, (Serializable) value); 381 } 382 383 public Serializable put(String key, Serializable value) { 384 Property p = doc.getProperty(key); 385 Serializable v = p.getValue(); 386 p.setValue(value); 387 return v; 388 } 389 390 @Override 391 public void putAll(Map<? extends String, ?> m) { 392 throw new UnsupportedOperationException("Read Only Map."); 393 } 394 395 @Override 396 public Serializable remove(Object key) { 397 throw new UnsupportedOperationException("Read Only Map."); 398 } 399 400 @Override 401 public void clear() { 402 throw new UnsupportedOperationException("Read Only Map."); 403 } 404 405 @Override 406 public String toString() { 407 return doc.toString(); 408 } 409 410}