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