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