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    @Override
239    public <T> List<T> getParameters(String name) {
240        if (StringUtils.isEmpty(name)) {
241            return null;
242        }
243        String realName = name.toLowerCase().trim();
244        @SuppressWarnings("unchecked")
245        List<T> values = (List<T>) parameters.get(realName);
246        if (values != null) {
247            return new ArrayList<T>(values);
248        } else {
249            return new ArrayList<T>();
250        }
251    }
252
253    @Override
254    public Map<String, List<Object>> getAllParameters() {
255        // make a copy of the local parameters
256        Map<String, List<Object>> unModifiableParameters = new HashMap<String, List<Object>>();
257        for (Map.Entry<String, List<Object>> entry : parameters.entrySet()) {
258            String key = entry.getKey();
259            List<Object> value = entry.getValue();
260            if (value == null) {
261                unModifiableParameters.put(key, null);
262            } else {
263                unModifiableParameters.put(key, new ArrayList<Object>(value));
264            }
265        }
266        return unModifiableParameters;
267    }
268
269    @Override
270    public void setParameterValues(String name, Object... values) {
271        if (StringUtils.isEmpty(name)) {
272            return;
273        }
274        String realName = name.toLowerCase().trim();
275        if (values.length == 0) {
276            parameters.remove(realName);
277            return;
278        }
279        setParameterListValues(realName, Arrays.asList(values));
280    }
281
282    @Override
283    public void setParameterListValues(String name, List<Object> values) {
284        if (StringUtils.isEmpty(name)) {
285            return;
286        }
287        String realName = name.toLowerCase().trim();
288        if (values == null) {
289            parameters.remove(realName);
290        }
291        parameters.put(realName, new CopyOnWriteArrayList<Object>(values));
292    }
293
294    @Override
295    public void addParameterValues(String name, Object... values) {
296        addParameterListValues(name, Arrays.asList(values));
297    }
298
299    @Override
300    public void addParameterListValues(String name, List<Object> values) {
301        if (StringUtils.isEmpty(name)) {
302            return;
303        }
304        String realName = name.toLowerCase().trim();
305        if (values == null) {
306            return;
307        }
308        List<Object> currentValues = parameters.get(realName);
309        if (currentValues == null) {
310            currentValues = new CopyOnWriteArrayList<Object>();
311            parameters.put(realName, currentValues);
312        }
313        for (Object value : values) {
314            currentValues.add(value);
315        }
316    }
317
318    static RenderingContextBuilder builder() {
319        return new RenderingContextBuilder();
320    }
321
322    public static final class RenderingContextBuilder {
323
324        private RenderingContextImpl ctx;
325
326        RenderingContextBuilder() {
327            ctx = new RenderingContextImpl();
328        }
329
330        public RenderingContextBuilder base(String url) {
331            ctx.baseUrl = url;
332            return this;
333        }
334
335        public RenderingContextBuilder locale(Locale locale) {
336            ctx.locale = locale;
337            return this;
338        }
339
340        public RenderingContextBuilder session(CoreSession session) {
341            ctx.session = session;
342            return this;
343        }
344
345        public RenderingContextBuilder param(String name, Object value) {
346            ctx.addParameterValues(name, value);
347            return this;
348        }
349
350        public RenderingContextBuilder paramValues(String name, Object... values) {
351            ctx.addParameterValues(name, values);
352            return this;
353        }
354
355        public RenderingContextBuilder paramList(String name, List<Object> values) {
356            ctx.addParameterListValues(name, values);
357            return this;
358        }
359
360        public RenderingContextBuilder properties(String... schemaName) {
361            return paramValues(EMBED_PROPERTIES, (Object[]) schemaName);
362        }
363
364        public RenderingContextBuilder enrich(String entityType, String... enricherName) {
365            return paramValues(EMBED_ENRICHERS + SEPARATOR + entityType, (Object[]) enricherName);
366        }
367
368        public RenderingContextBuilder enrichDoc(String... enricherName) {
369            return enrich(ENTITY_TYPE, enricherName);
370        }
371
372        public RenderingContextBuilder fetch(String entityType, String... propertyName) {
373            return paramValues(FETCH_PROPERTIES + SEPARATOR + entityType, (Object[]) propertyName);
374        }
375
376        public RenderingContextBuilder fetchInDoc(String... propertyName) {
377            return fetch(ENTITY_TYPE, propertyName);
378        }
379
380        public RenderingContextBuilder translate(String entityType, String... propertyName) {
381            return paramValues(TRANSLATE_PROPERTIES + SEPARATOR + entityType, (Object[]) propertyName);
382        }
383
384        public RenderingContextBuilder depth(DepthValues value) {
385            ctx.setParameterValues(MarshallingConstants.MAX_DEPTH_PARAM, value.name());
386            return this;
387        }
388
389        public RenderingContext get() {
390            return ctx;
391        }
392
393    }
394
395}