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