001/* 002 * (C) Copyright 2015-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 * Nicolas Chapurlat <nchapurlat@nuxeo.com> 018 */ 019package org.nuxeo.ecm.core.io.registry.context; 020 021import static org.nuxeo.ecm.core.io.marshallers.json.document.DocumentModelJsonWriter.ENTITY_TYPE; 022import static org.nuxeo.ecm.core.io.registry.MarshallingConstants.EMBED_ENRICHERS; 023import static org.nuxeo.ecm.core.io.registry.MarshallingConstants.EMBED_PROPERTIES; 024import static org.nuxeo.ecm.core.io.registry.MarshallingConstants.FETCH_PROPERTIES; 025import static org.nuxeo.ecm.core.io.registry.MarshallingConstants.HEADER_PREFIX; 026import static org.nuxeo.ecm.core.io.registry.MarshallingConstants.SEPARATOR; 027import static org.nuxeo.ecm.core.io.registry.MarshallingConstants.TRANSLATE_PROPERTIES; 028import static org.nuxeo.ecm.core.io.registry.MarshallingConstants.WRAPPED_CONTEXT; 029 030import java.util.ArrayList; 031import java.util.Arrays; 032import java.util.Collections; 033import java.util.HashMap; 034import java.util.List; 035import java.util.Locale; 036import java.util.Map; 037import java.util.Set; 038import java.util.TreeSet; 039import java.util.concurrent.ConcurrentHashMap; 040import java.util.concurrent.CopyOnWriteArrayList; 041import java.util.function.Supplier; 042 043import org.apache.commons.lang.StringUtils; 044import org.nuxeo.ecm.core.api.CoreInstance; 045import org.nuxeo.ecm.core.api.CoreSession; 046import org.nuxeo.ecm.core.api.DocumentModel; 047import org.nuxeo.ecm.core.io.registry.MarshallingConstants; 048import org.nuxeo.ecm.core.io.registry.MarshallingException; 049 050/** 051 * A thread-safe {@link RenderingContext} implementation. Please use {@link RenderingContext.CtxBuilder} to create 052 * instance of {@link RenderingContext}. 053 * 054 * @since 7.2 055 */ 056public class RenderingContextImpl implements RenderingContext { 057 058 private String baseUrl = DEFAULT_URL; 059 060 private Locale locale = DEFAULT_LOCALE; 061 062 private CoreSession session = null; 063 064 private Supplier<SessionWrapper> sessionWrapperSupplier; 065 066 private final Map<String, List<Object>> parameters = new ConcurrentHashMap<>(); 067 068 private RenderingContextImpl() { 069 } 070 071 @Override 072 public Locale getLocale() { 073 return locale; 074 } 075 076 @Override 077 public String getBaseUrl() { 078 return baseUrl; 079 } 080 081 @Override 082 public SessionWrapper getSession(DocumentModel document) { 083 if (document != null) { 084 CoreSession docSession = null; 085 try { 086 docSession = document.getCoreSession(); 087 } catch (UnsupportedOperationException e) { 088 // do nothing 089 } 090 if (docSession != null) { 091 return new SessionWrapper(docSession, false); 092 } 093 } 094 if (session != null) { 095 return new SessionWrapper(session, false); 096 } 097 098 if (sessionWrapperSupplier != null) { 099 SessionWrapper sessionWrapper = sessionWrapperSupplier.get(); 100 if (sessionWrapper != null) { 101 return sessionWrapper; 102 } 103 } 104 105 String repoNameFound = getParameter("X-NXRepository"); 106 if (StringUtils.isBlank(repoNameFound)) { 107 repoNameFound = getParameter("nxrepository"); 108 if (StringUtils.isBlank(repoNameFound)) { 109 try { 110 repoNameFound = document.getRepositoryName(); 111 } catch (UnsupportedOperationException e) { 112 // do nothing 113 } 114 } 115 } 116 if (!StringUtils.isBlank(repoNameFound)) { 117 CoreSession session = CoreInstance.openCoreSession(repoNameFound); 118 return new SessionWrapper(session, true); 119 } 120 throw new MarshallingException("Unable to create a new session"); 121 } 122 123 @Override 124 public void setExistingSession(CoreSession session) { 125 this.session = session; 126 } 127 128 @Override 129 public Set<String> getProperties() { 130 return getSplittedParameterValues(EMBED_PROPERTIES); 131 } 132 133 @Override 134 public Set<String> getFetched(String entity) { 135 return getSplittedParameterValues(FETCH_PROPERTIES, entity); 136 } 137 138 @Override 139 public Set<String> getTranslated(String entity) { 140 return getSplittedParameterValues(TRANSLATE_PROPERTIES, entity); 141 } 142 143 @Override 144 public Set<String> getEnrichers(String entity) { 145 return getSplittedParameterValues(EMBED_ENRICHERS, entity); 146 } 147 148 private Set<String> getSplittedParameterValues(String category, String... subCategories) { 149 // supports dot '.' as separator 150 Set<String> result = getSplittedParameterValues('.', category, subCategories); 151 // supports hyphen '-' as separator 152 result.addAll(getSplittedParameterValues(SEPARATOR, category, subCategories)); 153 return result; 154 } 155 156 @SuppressWarnings("deprecation") 157 private Set<String> getSplittedParameterValues(char separator, String category, String... subCategories) { 158 if (category == null) { 159 return Collections.emptySet(); 160 } 161 String paramKey = category; 162 for (String subCategory : subCategories) { 163 paramKey += separator + subCategory; 164 } 165 paramKey = paramKey.toLowerCase(); 166 List<Object> dirty = getParameters(paramKey); 167 dirty.addAll(getParameters(HEADER_PREFIX + paramKey)); 168 // Deprecated on server since 5.8, but the code on client wasn't - keep this part of code as Nuxeo Automation 169 // Client is deprecated since 8.10 and Nuxeo Java Client handle this properly 170 // backward compatibility, supports X-NXDocumentProperties and X-NXContext-Category 171 if (EMBED_PROPERTIES.toLowerCase().equals(paramKey)) { 172 dirty.addAll(getParameters("X-NXDocumentProperties")); 173 } else if ((EMBED_ENRICHERS + separator + ENTITY_TYPE).toLowerCase().equals(paramKey)) { 174 dirty.addAll(getParameters("X-NXContext-Category")); 175 } 176 Set<String> result = new TreeSet<String>(); 177 for (Object value : dirty) { 178 if (value instanceof String) { 179 result.addAll(Arrays.asList(org.nuxeo.common.utils.StringUtils.split((String) value, ',', true))); 180 } 181 } 182 return result; 183 } 184 185 private <T> T getWrappedEntity(String name) { 186 return WrappedContext.getEntity(this, name); 187 } 188 189 @Override 190 public WrappedContext wrap() { 191 return WrappedContext.create(this); 192 } 193 194 @Override 195 public <T> T getParameter(String name) { 196 if (StringUtils.isEmpty(name)) { 197 return null; 198 } 199 String realName = name.toLowerCase().trim(); 200 List<Object> values = parameters.get(realName); 201 if (values != null && values.size() > 0) { 202 @SuppressWarnings("unchecked") 203 T value = (T) values.get(0); 204 return value; 205 } 206 if (WRAPPED_CONTEXT.toLowerCase().equals(realName)) { 207 return null; 208 } else { 209 return getWrappedEntity(realName); 210 } 211 } 212 213 @Override 214 public boolean getBooleanParameter(String name) { 215 Object result = getParameter(name); 216 if (result == null) { 217 return false; 218 } else if (result instanceof Boolean) { 219 return (Boolean) result; 220 } else if (result instanceof String) { 221 try { 222 return Boolean.valueOf((String) result); 223 } catch (Exception e) { 224 return false; 225 } 226 } 227 return false; 228 } 229 230 @SuppressWarnings({ "rawtypes", "unchecked" }) 231 @Override 232 public <T> List<T> getParameters(String name) { 233 if (StringUtils.isEmpty(name)) { 234 return null; 235 } 236 String realName = name.toLowerCase().trim(); 237 List<T> values = (List<T>) parameters.get(realName); 238 List<T> result; 239 if (values != null) { 240 result = new ArrayList<>(values); 241 } else { 242 result = new ArrayList<>(); 243 } 244 if (WRAPPED_CONTEXT.toLowerCase().equals(realName)) { 245 return result; 246 } else { 247 Object wrapped = getWrappedEntity(realName); 248 if (wrapped == null) { 249 return result; 250 } 251 if (wrapped instanceof List) { 252 for (Object element : (List) wrapped) { 253 try { 254 T casted = (T) element; 255 result.add(casted); 256 } catch (ClassCastException e) { 257 return null; 258 } 259 } 260 } else { 261 try { 262 T casted = (T) wrapped; 263 result.add(casted); 264 } catch (ClassCastException e) { 265 return null; 266 } 267 } 268 } 269 return result; 270 } 271 272 @Override 273 public Map<String, List<Object>> getAllParameters() { 274 // make a copy of the local parameters 275 Map<String, List<Object>> unModifiableParameters = new HashMap<>(); 276 for (Map.Entry<String, List<Object>> entry : parameters.entrySet()) { 277 String key = entry.getKey(); 278 List<Object> value = entry.getValue(); 279 if (value == null) { 280 unModifiableParameters.put(key, null); 281 } else { 282 unModifiableParameters.put(key, new ArrayList<>(value)); 283 } 284 } 285 return unModifiableParameters; 286 } 287 288 @Override 289 public void setParameterValues(String name, Object... values) { 290 if (StringUtils.isEmpty(name)) { 291 return; 292 } 293 String realName = name.toLowerCase().trim(); 294 if (values.length == 0) { 295 parameters.remove(realName); 296 return; 297 } 298 setParameterListValues(realName, Arrays.asList(values)); 299 } 300 301 @Override 302 public void setParameterListValues(String name, List<Object> values) { 303 if (StringUtils.isEmpty(name)) { 304 return; 305 } 306 String realName = name.toLowerCase().trim(); 307 if (values == null) { 308 parameters.remove(realName); 309 } 310 parameters.put(realName, new CopyOnWriteArrayList<>(values)); 311 } 312 313 @Override 314 public void addParameterValues(String name, Object... values) { 315 addParameterListValues(name, Arrays.asList(values)); 316 } 317 318 @Override 319 public void addParameterListValues(String name, List<Object> values) { 320 if (StringUtils.isEmpty(name)) { 321 return; 322 } 323 String realName = name.toLowerCase().trim(); 324 if (values == null) { 325 return; 326 } 327 parameters.computeIfAbsent(realName, key -> new CopyOnWriteArrayList()).addAll(values); 328 } 329 330 static RenderingContextBuilder builder() { 331 return new RenderingContextBuilder(); 332 } 333 334 public static final class RenderingContextBuilder { 335 336 private RenderingContextImpl ctx; 337 338 RenderingContextBuilder() { 339 ctx = new RenderingContextImpl(); 340 } 341 342 public RenderingContextBuilder base(String url) { 343 ctx.baseUrl = url; 344 return this; 345 } 346 347 public RenderingContextBuilder locale(Locale locale) { 348 ctx.locale = locale; 349 return this; 350 } 351 352 public RenderingContextBuilder session(CoreSession session) { 353 ctx.session = session; 354 return this; 355 } 356 357 /** 358 * @since 9.3 359 */ 360 public RenderingContextBuilder sessionWrapperSupplier(Supplier<SessionWrapper> supplier) { 361 ctx.sessionWrapperSupplier = supplier; 362 return this; 363 } 364 365 public RenderingContextBuilder param(String name, Object value) { 366 ctx.addParameterValues(name, value); 367 return this; 368 } 369 370 public RenderingContextBuilder paramValues(String name, Object... values) { 371 ctx.addParameterValues(name, values); 372 return this; 373 } 374 375 public RenderingContextBuilder paramList(String name, List<Object> values) { 376 ctx.addParameterListValues(name, values); 377 return this; 378 } 379 380 public RenderingContextBuilder properties(String... schemaName) { 381 return paramValues(EMBED_PROPERTIES, (Object[]) schemaName); 382 } 383 384 public RenderingContextBuilder enrich(String entityType, String... enricherName) { 385 return paramValues(EMBED_ENRICHERS + SEPARATOR + entityType, (Object[]) enricherName); 386 } 387 388 public RenderingContextBuilder enrichDoc(String... enricherName) { 389 return enrich(ENTITY_TYPE, enricherName); 390 } 391 392 public RenderingContextBuilder fetch(String entityType, String... propertyName) { 393 return paramValues(FETCH_PROPERTIES + SEPARATOR + entityType, (Object[]) propertyName); 394 } 395 396 public RenderingContextBuilder fetchInDoc(String... propertyName) { 397 return fetch(ENTITY_TYPE, propertyName); 398 } 399 400 public RenderingContextBuilder translate(String entityType, String... propertyName) { 401 return paramValues(TRANSLATE_PROPERTIES + SEPARATOR + entityType, (Object[]) propertyName); 402 } 403 404 public RenderingContextBuilder depth(DepthValues value) { 405 ctx.setParameterValues(MarshallingConstants.MAX_DEPTH_PARAM, value.name()); 406 return this; 407 } 408 409 public RenderingContext get() { 410 return ctx; 411 } 412 413 } 414 415}