001/*
002 * (C) Copyright 2010 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 *     Anahide Tchertchian
018 */
019package org.nuxeo.ecm.platform.contentview.seam;
020
021import static org.jboss.seam.ScopeType.CONVERSATION;
022
023import java.io.Serializable;
024import java.io.UnsupportedEncodingException;
025import java.util.ArrayList;
026import java.util.Collections;
027import java.util.List;
028
029import javax.faces.context.FacesContext;
030import javax.faces.model.SelectItem;
031
032import org.jboss.seam.annotations.In;
033import org.jboss.seam.annotations.Name;
034import org.jboss.seam.annotations.Scope;
035import org.jboss.seam.annotations.intercept.BypassInterceptors;
036import org.nuxeo.ecm.core.api.CoreSession;
037import org.nuxeo.ecm.core.api.DocumentModel;
038import org.nuxeo.ecm.core.api.NuxeoPrincipal;
039import org.nuxeo.ecm.core.api.SortInfo;
040import org.nuxeo.ecm.platform.actions.Action;
041import org.nuxeo.ecm.platform.actions.ActionContext;
042import org.nuxeo.ecm.platform.actions.jsf.JSFActionContext;
043import org.nuxeo.ecm.platform.actions.seam.SeamActionContext;
044import org.nuxeo.ecm.platform.contentview.jsf.ContentView;
045import org.nuxeo.ecm.platform.contentview.jsf.ContentViewCache;
046import org.nuxeo.ecm.platform.contentview.jsf.ContentViewService;
047import org.nuxeo.ecm.platform.contentview.jsf.ContentViewState;
048import org.nuxeo.ecm.platform.query.api.PageProvider;
049import org.nuxeo.ecm.platform.ui.web.api.WebActions;
050import org.nuxeo.ecm.platform.ui.web.util.SeamContextHelper;
051
052/**
053 * Handles cache and refresh for named content views.
054 *
055 * @author Anahide Tchertchian
056 * @since 5.4
057 * @since 5.4.2: moved to content-view-jsf module
058 */
059@Name("contentViewActions")
060@Scope(CONVERSATION)
061public class ContentViewActions implements Serializable {
062
063    private static final long serialVersionUID = 1L;
064
065    @In(create = true)
066    protected ContentViewRestActions contentViewRestActions;
067
068    @In(create = true)
069    protected ContentViewService contentViewService;
070
071    protected final ContentViewCache cache = new ContentViewCache();
072
073    protected Long globalPageSize;
074
075    @In(create = true)
076    private transient NuxeoPrincipal currentNuxeoPrincipal;
077
078    @In(create = true, required = false)
079    private transient CoreSession documentManager;
080
081    @In(create = true, required = false)
082    protected transient WebActions webActions;
083
084    protected ContentView currentContentView;
085
086    /**
087     * Returns the current global content view.
088     * <p>
089     * Current content view is usually the last one displayed on the page, but this may change depending on calls to
090     * {@link #setCurrentContentView(ContentView)}.
091     */
092    public ContentView getCurrentContentView() {
093        return currentContentView;
094    }
095
096    /**
097     * Sets the current global content view.
098     *
099     * @see #getCurrentGlobalPageSize()
100     */
101    public void setCurrentContentView(ContentView cv) {
102        currentContentView = cv;
103    }
104
105    /**
106     * Returns the global page size, or returns the page size on current content view if set.
107     */
108    public Long getCurrentGlobalPageSize() {
109        if (currentContentView != null && currentContentView.getUseGlobalPageSize()) {
110            return currentContentView.getCurrentPageSize();
111        }
112        return globalPageSize;
113    }
114
115    /**
116     * Sets the global page size, useful to set the value having the appropriate selection set, see
117     * {@link #getCurrentContentView()}
118     */
119    public void setCurrentGlobalPageSize(Long pageSize) {
120        globalPageSize = pageSize;
121    }
122
123    /**
124     * Returns the global page size
125     */
126    public Long getGlobalPageSize() {
127        return globalPageSize;
128    }
129
130    /**
131     * Returns the list of available options for page size selections, for given content view.
132     * <p>
133     * This method relies on a hard-coded set of available values, and adapts it to the current content view page size
134     * and max page size information to present more or less items from this list.
135     *
136     * @since 5.8
137     */
138    @SuppressWarnings("boxing")
139    public List<SelectItem> getPageSizeOptions(ContentView cv) {
140        List<SelectItem> items = new ArrayList<SelectItem>();
141        if (cv == null) {
142            return items;
143        }
144        List<Long> values = new ArrayList<Long>();
145        long maxPageSize = 0;
146        PageProvider<?> pp = cv.getCurrentPageProvider();
147        if (pp != null) {
148            // include original page size options set on the page provider definition, as well as current page size if
149            // not present
150            List<Long> options = pp.getPageSizeOptions();
151            if (options != null) {
152                values.addAll(options);
153            }
154            maxPageSize = pp.getMaxPageSize();
155        }
156        if (cv.getUseGlobalPageSize()) {
157            // add the global page size if not present
158            Long globalSize = getGlobalPageSize();
159            if (globalSize != null && globalSize > 0 && !values.contains(globalSize)) {
160                values.add(globalSize);
161            }
162        }
163        Collections.sort(values);
164        for (Long value : values) {
165            // remove every element that would be larger than max page size
166            if (maxPageSize > 0 && maxPageSize < value) {
167                break;
168            }
169            items.add(new SelectItem(value));
170        }
171        // make sure the list is not empty
172        if (items.isEmpty()) {
173            if (maxPageSize > 0) {
174                items.add(new SelectItem(maxPageSize));
175            } else {
176                items.add(new SelectItem(1));
177            }
178        }
179        return items;
180    }
181
182    /**
183     * Sets the global page size
184     */
185    public void setGlobalPageSize(Long pageSize) {
186        globalPageSize = pageSize;
187    }
188
189    public ContentView getContentView(String name) {
190        return getContentView(name, null);
191    }
192
193    /**
194     * Returns content view with given name, or null if no content view with this name is found.
195     * <p>
196     * If parameter searchDocumentModel is not null, it will be set on the content view. If it is null and the content
197     * is using a provider that needs it, a new document model is created and attached to it. This document model is
198     * resolved from the binding put in the content view XML definition, or from the document type in this definition if
199     * no binding is set.
200     * <p>
201     * If not null, this content view is set as the current content view so that subsequent calls to other methods can
202     * take information from it, like {@link #getCurrentGlobalPageSize()}
203     * <p>
204     * The content view is put in a cache map so that it's not rebuilt at each call. It is rebuilt when its cache key
205     * changes (if defined).
206     */
207    public ContentView getContentView(String name, DocumentModel searchDocumentModel) {
208        ContentView cView = cache.get(name);
209        if (cView == null) {
210            cView = contentViewService.getContentView(name);
211            if (cView != null) {
212                cache.add(cView);
213            }
214        }
215        if (cView != null) {
216            if (searchDocumentModel != null) {
217                cView.setSearchDocumentModel(searchDocumentModel);
218            }
219            setCurrentContentView(cView);
220        }
221        return cView;
222    }
223
224    public ContentView getContentViewWithProvider(String name) {
225        return getContentViewWithProvider(name, null, null, null, null);
226    }
227
228    public ContentView getContentViewWithProvider(String name, DocumentModel searchDocumentModel) {
229        return getContentViewWithProvider(name, searchDocumentModel, null, null, null);
230    }
231
232    public ContentView getContentViewWithProvider(String name, DocumentModel searchDocumentModel,
233            List<SortInfo> sortInfos, Long pageSize, Long currentPage) {
234        return getContentViewWithProvider(name, searchDocumentModel, sortInfos, pageSize, currentPage, (Object[]) null);
235    }
236
237    public ContentView getContentViewWithProvider(String name, DocumentModel searchDocumentModel,
238            List<SortInfo> sortInfos, Long defaultPageSize, Long pageSize, Long currentPage) {
239        return getContentViewWithProvider(name, searchDocumentModel, sortInfos, defaultPageSize, pageSize, currentPage,
240                (Object[]) null);
241    }
242
243    /**
244     * @since 5.6
245     */
246    public ContentView getContentViewWithProvider(String name, DocumentModel searchDocumentModel,
247            List<SortInfo> sortInfos, Long pageSize, Long currentPage, Object... params) {
248        return getContentViewWithProvider(name, searchDocumentModel, sortInfos, Long.valueOf(-1), pageSize, currentPage,
249                params);
250    }
251
252    /**
253     * Helper method to retrieve a content view, taking care of initialization of page provider according to parameters
254     * and current global page size.
255     * <p>
256     * This method is not public to avoid EL method resolution issues.
257     */
258    protected ContentView getContentViewWithProvider(String name, DocumentModel searchDocumentModel,
259            List<SortInfo> sortInfos, Long defaultPageSize, Long pageSize, Long currentPage, Object... params) {
260        ContentView cView = getContentView(name, searchDocumentModel);
261        if (cView != null) {
262            if (cView.getUseGlobalPageSize()) {
263                cView.setCurrentPageSize(globalPageSize);
264            }
265            if (cView.getCurrentPageSize() == null && defaultPageSize != null && defaultPageSize.longValue() >= 0) {
266                cView.setCurrentPageSize(defaultPageSize);
267            }
268            // initialize provider
269            cView.getPageProvider(searchDocumentModel, sortInfos, pageSize, currentPage, params);
270        }
271        return cView;
272    }
273
274    /**
275     * Restore a content view from the given parameters.
276     * <p>
277     * The content view is put in a cache map so that it's not rebuilt at each call. It is rebuilt when its cache key
278     * changes (if defined).
279     *
280     * @since 5.7
281     */
282    public ContentView restoreContentView(String contentViewName, Long currentPage, Long pageSize,
283            List<SortInfo> sortInfos, String jsonContentViewState) throws UnsupportedEncodingException {
284        ContentView cv = contentViewRestActions.restoreContentView(contentViewName, currentPage, pageSize, sortInfos,
285                jsonContentViewState);
286        cache.add(cv);
287        return cv;
288    }
289
290    /**
291     * Restore a Content View from the given ContentView state.
292     * <p>
293     * The content view is put in a cache map so that it's not rebuilt at each call. It is rebuilt when its cache key
294     * changes (if defined).
295     *
296     * @since 6.0
297     */
298    public ContentView restoreContentView(ContentViewState state) throws UnsupportedEncodingException {
299        ContentView cv = contentViewService.restoreContentView(state);
300        cache.add(cv);
301        return cv;
302    }
303
304    /**
305     * Refreshes all content views that have declared the given seam event name as a refresh event in their XML
306     * configuration.
307     */
308    @BypassInterceptors
309    public void refreshOnSeamEvent(String seamEventName) {
310        cache.refreshOnEvent(seamEventName);
311    }
312
313    /**
314     * Resets all content views page providers that have declared the given seam event name as a reset event in their
315     * XML configuration.
316     */
317    @BypassInterceptors
318    public void resetPageProviderOnSeamEvent(String seamEventName) {
319        cache.resetPageProviderOnEvent(seamEventName);
320    }
321
322    @BypassInterceptors
323    public void refresh(String contentViewName) {
324        cache.refresh(contentViewName, false);
325    }
326
327    @BypassInterceptors
328    public void refreshAndRewind(String contentViewName) {
329        cache.refresh(contentViewName, true);
330    }
331
332    /**
333     * @since 6.0
334     */
335    @BypassInterceptors
336    public void resetAggregates(String contentViewName) {
337        cache.resetPageProviderAggregates(contentViewName);
338    }
339
340    @BypassInterceptors
341    public void resetPageProvider(String contentViewName) {
342        cache.resetPageProvider(contentViewName);
343    }
344
345    @BypassInterceptors
346    public void reset(String contentViewName) {
347        cache.reset(contentViewName);
348    }
349
350    @BypassInterceptors
351    public void resetAllContent() {
352        cache.resetAllContent();
353    }
354
355    @BypassInterceptors
356    public void resetAll() {
357        cache.resetAll();
358    }
359
360    /**
361     * @since 5.7
362     */
363    @BypassInterceptors
364    public void refreshAll() {
365        cache.refreshAll();
366    }
367
368    /**
369     * @since 5.7
370     */
371    @BypassInterceptors
372    public void refreshAndRewindAll() {
373        cache.refreshAndRewindAll();
374    }
375
376    /**
377     * Returns actions filtered depending on given custom context.
378     * <p>
379     * Boolean values are declared as objects to avoid conversion to "false" when variable is not defined, and keep
380     * "null" value.
381     *
382     * @since 6.0
383     */
384    public List<Action> getActionsList(String category, DocumentModel currentDocument, ContentView contentView,
385            Object showPageSizeSelector, Object showRefreshCommand, Object showCSVExport, Object showPDFExport,
386            Object showSyndicationLinks, Object showSlideshow, Object showEditColumns, Object showEditRows,
387            Object showSpreadsheet) {
388        return webActions.getActionsList(category,
389                createContentViewActionContext(currentDocument, contentView, showPageSizeSelector, showRefreshCommand,
390                        showCSVExport, showPDFExport, showSyndicationLinks, showSlideshow, showEditColumns,
391                        showEditRows, showSpreadsheet));
392    }
393
394    protected ActionContext createContentViewActionContext(DocumentModel currentDocument, ContentView contentView,
395            Object showPageSizeSelector, Object showRefreshCommand, Object showCSVExport, Object showPDFExport,
396            Object showSyndicationLinks, Object showSlideshow, Object showEditColumns, Object showEditRows,
397            Object showSpreadsheet) {
398        ActionContext ctx;
399        FacesContext faces = FacesContext.getCurrentInstance();
400        if (faces == null) {
401            ctx = new SeamActionContext();
402        } else {
403            ctx = new JSFActionContext(faces);
404        }
405        ctx.setCurrentPrincipal(currentNuxeoPrincipal);
406        ctx.setDocumentManager(documentManager);
407        ctx.setCurrentDocument(currentDocument);
408        ctx.putLocalVariable("SeamContext", new SeamContextHelper());
409        ctx.putLocalVariable("contentView", contentView);
410        // additional local variables for action filters
411        ctx.putLocalVariable("showPageSizeSelector", showPageSizeSelector);
412        ctx.putLocalVariable("showRefreshCommand", showRefreshCommand);
413        ctx.putLocalVariable("showCSVExport", showCSVExport);
414        ctx.putLocalVariable("showPDFExport", showPDFExport);
415        ctx.putLocalVariable("showSyndicationLinks", showSyndicationLinks);
416        ctx.putLocalVariable("showSlideshow", showSlideshow);
417        ctx.putLocalVariable("showEditColumns", showEditColumns);
418        ctx.putLocalVariable("showEditRows", showEditRows);
419        ctx.putLocalVariable("showSpreadsheet", showSpreadsheet);
420        return ctx;
421    }
422}