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