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