/*
* (C) Copyright 2015-2016 Nuxeo SA (http://nuxeo.com/) and others.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Contributors:
* Nicolas Chapurlat <nchapurlat@nuxeo.com>
*/
package org.nuxeo.ecm.core.io.registry.context;
import static org.nuxeo.ecm.core.io.marshallers.json.document.DocumentModelJsonWriter.ENTITY_TYPE;
import static org.nuxeo.ecm.core.io.registry.MarshallingConstants.EMBED_ENRICHERS;
import static org.nuxeo.ecm.core.io.registry.MarshallingConstants.EMBED_PROPERTIES;
import static org.nuxeo.ecm.core.io.registry.MarshallingConstants.FETCH_PROPERTIES;
import static org.nuxeo.ecm.core.io.registry.MarshallingConstants.HEADER_PREFIX;
import static org.nuxeo.ecm.core.io.registry.MarshallingConstants.SEPARATOR;
import static org.nuxeo.ecm.core.io.registry.MarshallingConstants.TRANSLATE_PROPERTIES;
import static org.nuxeo.ecm.core.io.registry.MarshallingConstants.WRAPPED_CONTEXT;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import org.apache.commons.lang.StringUtils;
import org.nuxeo.ecm.core.api.CoreInstance;
import org.nuxeo.ecm.core.api.CoreSession;
import org.nuxeo.ecm.core.api.DocumentModel;
import org.nuxeo.ecm.core.io.registry.MarshallingConstants;
import org.nuxeo.ecm.core.io.registry.MarshallingException;
/**
* A thread-safe {@link RenderingContext} implementation. Please use {@link RenderingContext.CtxBuilder} to create
* instance of {@link RenderingContext}.
*
* @since 7.2
*/
public class RenderingContextImpl implements RenderingContext {
private String baseUrl = DEFAULT_URL;
private Locale locale = DEFAULT_LOCALE;
private CoreSession session = null;
private final Map<String, List<Object>> parameters = new ConcurrentHashMap<>();
private RenderingContextImpl() {
}
@Override
public Locale getLocale() {
return locale;
}
@Override
public String getBaseUrl() {
return baseUrl;
}
@Override
public SessionWrapper getSession(DocumentModel document) {
if (document != null) {
CoreSession docSession = null;
try {
docSession = document.getCoreSession();
} catch (UnsupportedOperationException e) {
// do nothing
}
if (docSession != null) {
return new SessionWrapper(docSession, false);
}
}
if (session != null) {
return new SessionWrapper(session, false);
}
String repoNameFound = getParameter("X-NXRepository");
if (StringUtils.isBlank(repoNameFound)) {
repoNameFound = getParameter("nxrepository");
if (StringUtils.isBlank(repoNameFound)) {
try {
repoNameFound = document.getRepositoryName();
} catch (UnsupportedOperationException e) {
// do nothing
}
}
}
if (!StringUtils.isBlank(repoNameFound)) {
CoreSession session = CoreInstance.openCoreSession(repoNameFound);
return new SessionWrapper(session, true);
}
throw new MarshallingException("Unable to create a new session");
}
@Override
public void setExistingSession(CoreSession session) {
this.session = session;
}
@Override
public Set<String> getProperties() {
return getSplittedParameterValues(EMBED_PROPERTIES);
}
@Override
public Set<String> getFetched(String entity) {
return getSplittedParameterValues(FETCH_PROPERTIES, entity);
}
@Override
public Set<String> getTranslated(String entity) {
return getSplittedParameterValues(TRANSLATE_PROPERTIES, entity);
}
@Override
public Set<String> getEnrichers(String entity) {
return getSplittedParameterValues(EMBED_ENRICHERS, entity);
}
private Set<String> getSplittedParameterValues(String category, String... subCategories) {
// supports dot '.' as separator
Set<String> result = getSplittedParameterValues('.', category, subCategories);
// supports hyphen '-' as separator
result.addAll(getSplittedParameterValues(SEPARATOR, category, subCategories));
return result;
}
@SuppressWarnings("deprecation")
private Set<String> getSplittedParameterValues(char separator, String category, String... subCategories) {
if (category == null) {
return Collections.emptySet();
}
String paramKey = category;
for (String subCategory : subCategories) {
paramKey += separator + subCategory;
}
paramKey = paramKey.toLowerCase();
List<Object> dirty = getParameters(paramKey);
dirty.addAll(getParameters(HEADER_PREFIX + paramKey));
// Deprecated on server since 5.8, but the code on client wasn't - keep this part of code as Nuxeo Automation
// Client is deprecated since 8.10 and Nuxeo Java Client handle this properly
// backward compatibility, supports X-NXDocumentProperties and X-NXContext-Category
if (EMBED_PROPERTIES.toLowerCase().equals(paramKey)) {
dirty.addAll(getParameters("X-NXDocumentProperties"));
} else if ((EMBED_ENRICHERS + separator + ENTITY_TYPE).toLowerCase().equals(paramKey)) {
dirty.addAll(getParameters("X-NXContext-Category"));
}
Set<String> result = new TreeSet<String>();
for (Object value : dirty) {
if (value instanceof String) {
result.addAll(Arrays.asList(org.nuxeo.common.utils.StringUtils.split((String) value, ',', true)));
}
}
return result;
}
private <T> T getWrappedEntity(String name) {
return WrappedContext.getEntity(this, name);
}
@Override
public WrappedContext wrap() {
return WrappedContext.create(this);
}
@Override
public <T> T getParameter(String name) {
if (StringUtils.isEmpty(name)) {
return null;
}
String realName = name.toLowerCase().trim();
List<Object> values = parameters.get(realName);
if (values != null && values.size() > 0) {
@SuppressWarnings("unchecked")
T value = (T) values.get(0);
return value;
}
if (WRAPPED_CONTEXT.toLowerCase().equals(realName)) {
return null;
} else {
return getWrappedEntity(realName);
}
}
@Override
public boolean getBooleanParameter(String name) {
Object result = getParameter(name);
if (result == null) {
return false;
} else if (result instanceof Boolean) {
return (Boolean) result;
} else if (result instanceof String) {
try {
return Boolean.valueOf((String) result);
} catch (Exception e) {
return false;
}
}
return false;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
@Override
public <T> List<T> getParameters(String name) {
if (StringUtils.isEmpty(name)) {
return null;
}
String realName = name.toLowerCase().trim();
List<T> values = (List<T>) parameters.get(realName);
List<T> result;
if (values != null) {
result = new ArrayList<>(values);
} else {
result = new ArrayList<>();
}
if (WRAPPED_CONTEXT.toLowerCase().equals(realName)) {
return result;
} else {
Object wrapped = getWrappedEntity(realName);
if (wrapped == null) {
return result;
}
if (wrapped instanceof List) {
for (Object element : (List) wrapped) {
try {
T casted = (T) element;
result.add(casted);
} catch (ClassCastException e) {
return null;
}
}
} else {
try {
T casted = (T) wrapped;
result.add(casted);
} catch (ClassCastException e) {
return null;
}
}
}
return result;
}
@Override
public Map<String, List<Object>> getAllParameters() {
// make a copy of the local parameters
Map<String, List<Object>> unModifiableParameters = new HashMap<>();
for (Map.Entry<String, List<Object>> entry : parameters.entrySet()) {
String key = entry.getKey();
List<Object> value = entry.getValue();
if (value == null) {
unModifiableParameters.put(key, null);
} else {
unModifiableParameters.put(key, new ArrayList<>(value));
}
}
return unModifiableParameters;
}
@Override
public void setParameterValues(String name, Object... values) {
if (StringUtils.isEmpty(name)) {
return;
}
String realName = name.toLowerCase().trim();
if (values.length == 0) {
parameters.remove(realName);
return;
}
setParameterListValues(realName, Arrays.asList(values));
}
@Override
public void setParameterListValues(String name, List<Object> values) {
if (StringUtils.isEmpty(name)) {
return;
}
String realName = name.toLowerCase().trim();
if (values == null) {
parameters.remove(realName);
}
parameters.put(realName, new CopyOnWriteArrayList<>(values));
}
@Override
public void addParameterValues(String name, Object... values) {
addParameterListValues(name, Arrays.asList(values));
}
@Override
public void addParameterListValues(String name, List<Object> values) {
if (StringUtils.isEmpty(name)) {
return;
}
String realName = name.toLowerCase().trim();
if (values == null) {
return;
}
parameters.computeIfAbsent(realName, key -> new CopyOnWriteArrayList()).addAll(values);
}
static RenderingContextBuilder builder() {
return new RenderingContextBuilder();
}
public static final class RenderingContextBuilder {
private RenderingContextImpl ctx;
RenderingContextBuilder() {
ctx = new RenderingContextImpl();
}
public RenderingContextBuilder base(String url) {
ctx.baseUrl = url;
return this;
}
public RenderingContextBuilder locale(Locale locale) {
ctx.locale = locale;
return this;
}
public RenderingContextBuilder session(CoreSession session) {
ctx.session = session;
return this;
}
public RenderingContextBuilder param(String name, Object value) {
ctx.addParameterValues(name, value);
return this;
}
public RenderingContextBuilder paramValues(String name, Object... values) {
ctx.addParameterValues(name, values);
return this;
}
public RenderingContextBuilder paramList(String name, List<Object> values) {
ctx.addParameterListValues(name, values);
return this;
}
public RenderingContextBuilder properties(String... schemaName) {
return paramValues(EMBED_PROPERTIES, (Object[]) schemaName);
}
public RenderingContextBuilder enrich(String entityType, String... enricherName) {
return paramValues(EMBED_ENRICHERS + SEPARATOR + entityType, (Object[]) enricherName);
}
public RenderingContextBuilder enrichDoc(String... enricherName) {
return enrich(ENTITY_TYPE, enricherName);
}
public RenderingContextBuilder fetch(String entityType, String... propertyName) {
return paramValues(FETCH_PROPERTIES + SEPARATOR + entityType, (Object[]) propertyName);
}
public RenderingContextBuilder fetchInDoc(String... propertyName) {
return fetch(ENTITY_TYPE, propertyName);
}
public RenderingContextBuilder translate(String entityType, String... propertyName) {
return paramValues(TRANSLATE_PROPERTIES + SEPARATOR + entityType, (Object[]) propertyName);
}
public RenderingContextBuilder depth(DepthValues value) {
ctx.setParameterValues(MarshallingConstants.MAX_DEPTH_PARAM, value.name());
return this;
}
public RenderingContext get() {
return ctx;
}
}
}