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