001/*
002 * (C) Copyright 2010 Nuxeo SA (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     Anahide Tchertchian
016 */
017package org.nuxeo.ecm.platform.contentview.jsf;
018
019import java.io.Serializable;
020import java.util.ArrayList;
021import java.util.Arrays;
022import java.util.Collections;
023import java.util.HashMap;
024import java.util.HashSet;
025import java.util.LinkedHashSet;
026import java.util.List;
027import java.util.Map;
028import java.util.Set;
029
030import javax.faces.context.FacesContext;
031
032import org.apache.commons.logging.Log;
033import org.apache.commons.logging.LogFactory;
034import org.nuxeo.ecm.core.api.DocumentModel;
035import org.nuxeo.ecm.core.api.NuxeoException;
036import org.nuxeo.ecm.core.api.SortInfo;
037import org.nuxeo.ecm.platform.query.api.PageProvider;
038import org.nuxeo.ecm.platform.query.api.PageProviderDefinition;
039import org.nuxeo.ecm.platform.query.api.PageProviderService;
040import org.nuxeo.ecm.platform.query.core.CoreQueryPageProviderDescriptor;
041import org.nuxeo.ecm.platform.query.core.GenericPageProviderDescriptor;
042import org.nuxeo.ecm.platform.query.core.ReferencePageProviderDescriptor;
043import org.nuxeo.ecm.platform.ui.web.util.ComponentTagUtils;
044import org.nuxeo.runtime.api.Framework;
045import org.nuxeo.runtime.model.ComponentInstance;
046import org.nuxeo.runtime.model.DefaultComponent;
047
048/**
049 * @author Anahide Tchertchian
050 * @since 5.4
051 */
052public class ContentViewServiceImpl extends DefaultComponent implements ContentViewService {
053
054    public static final String CONTENT_VIEW_EP = "contentViews";
055
056    private static final long serialVersionUID = 1L;
057
058    private static final Log log = LogFactory.getLog(ContentViewServiceImpl.class);
059
060    protected ContentViewRegistry contentViewReg = new ContentViewRegistry();
061
062    @Override
063    public ContentView getContentView(String name) {
064        ContentViewDescriptor desc = contentViewReg.getContentView(name);
065        if (desc == null) {
066            return null;
067        }
068        Boolean useGlobalPageSize = desc.getUseGlobalPageSize();
069        if (useGlobalPageSize == null) {
070            useGlobalPageSize = Boolean.FALSE;
071        }
072        Boolean translateTitle = desc.getTranslateTitle();
073        if (translateTitle == null) {
074            translateTitle = Boolean.FALSE;
075        }
076        Boolean translateEmptySentence = desc.getTranslateEmptySentence();
077        if (translateEmptySentence == null) {
078            translateEmptySentence = Boolean.FALSE;
079        }
080        Boolean showTitle = desc.getShowTitle();
081        if (showTitle == null) {
082            showTitle = Boolean.FALSE;
083        }
084        Boolean showPageSizeSelector = desc.getShowPageSizeSelector();
085        if (showPageSizeSelector == null) {
086            showPageSizeSelector = Boolean.FALSE;
087        }
088        Boolean showRefreshPage = desc.getShowRefreshCommand();
089        if (showRefreshPage == null) {
090            showRefreshPage = Boolean.TRUE;
091        }
092        Boolean showFilterForm = desc.getShowFilterForm();
093        if (showFilterForm == null) {
094            showFilterForm = Boolean.FALSE;
095        }
096
097        String[] queryParams = null;
098        String searchDocumentType = null;
099        String sortInfosBinding = null;
100        String pageSizeBinding = null;
101        CoreQueryPageProviderDescriptor coreDesc = desc.getCoreQueryPageProvider();
102        GenericPageProviderDescriptor genDesc = desc.getGenericPageProvider();
103        ReferencePageProviderDescriptor refDesc = desc.getReferencePageProvider();
104        String[] refQueryParams = null;
105        if (refDesc != null && refDesc.isEnabled()) {
106            PageProviderService ppService = Framework.getLocalService(PageProviderService.class);
107            PageProviderDefinition def = ppService.getPageProviderDefinition(refDesc.getName());
108            if (def == null) {
109                log.error("Could not resolve page provider with name " + refDesc.getName());
110            } else if (def instanceof CoreQueryPageProviderDescriptor) {
111                coreDesc = (CoreQueryPageProviderDescriptor) def;
112                refQueryParams = refDesc.getQueryParameters();
113            } else if (def instanceof GenericPageProviderDescriptor) {
114                genDesc = (GenericPageProviderDescriptor) def;
115                refQueryParams = refDesc.getQueryParameters();
116            }
117        }
118        if (coreDesc != null && coreDesc.isEnabled()) {
119            queryParams = coreDesc.getQueryParameters();
120            sortInfosBinding = coreDesc.getSortInfosBinding();
121            pageSizeBinding = coreDesc.getPageSizeBinding();
122            searchDocumentType = coreDesc.getSearchDocumentType();
123        } else if (genDesc != null && genDesc.isEnabled()) {
124            queryParams = genDesc.getQueryParameters();
125            sortInfosBinding = genDesc.getSortInfosBinding();
126            pageSizeBinding = genDesc.getPageSizeBinding();
127            searchDocumentType = genDesc.getSearchDocumentType();
128        }
129        List<String> allQueryParams = new ArrayList<String>();
130        if (queryParams != null) {
131            allQueryParams.addAll(Arrays.asList(queryParams));
132        }
133        if (refQueryParams != null) {
134            allQueryParams.addAll(Arrays.asList(refQueryParams));
135        }
136        String searchDocBinding = desc.getSearchDocumentBinding();
137        ContentViewImpl contentView = new ContentViewImpl(name, desc.getTitle(), translateTitle.booleanValue(),
138                desc.getIconPath(), desc.getSelectionListName(), desc.getPagination(), desc.getActionCategories(),
139                desc.getSearchLayout(), desc.getResultLayouts(), desc.getFlags(), desc.getCacheKey(),
140                desc.getCacheSize(), desc.getRefreshEventNames(), desc.getResetEventNames(),
141                useGlobalPageSize.booleanValue(), allQueryParams.toArray(new String[] {}), searchDocBinding,
142                searchDocumentType, desc.getResultColumnsBinding(), desc.getResultLayoutBinding(), sortInfosBinding,
143                pageSizeBinding, showTitle.booleanValue(), showPageSizeSelector.booleanValue(),
144                showRefreshPage.booleanValue(), showFilterForm.booleanValue(), desc.getEmptySentence(),
145                translateEmptySentence.booleanValue());
146        contentView.setWaitForExecutionSentence(desc.getWaitForExecutionSentence());
147        if (desc.getWaitForExecution() != null) {
148            contentView.setWaitForExecution(desc.getWaitForExecution().booleanValue());
149        }
150        return contentView;
151    }
152
153    protected ContentViewHeader getContentViewHeader(ContentViewDescriptor desc) {
154        return new ContentViewHeader(desc.getName(), desc.getTitle(), Boolean.TRUE.equals(desc.getTranslateTitle()),
155                desc.getIconPath());
156    }
157
158    @Override
159    public ContentViewHeader getContentViewHeader(String name) {
160        ContentViewDescriptor desc = contentViewReg.getContentView(name);
161        if (desc == null) {
162            return null;
163        }
164        return getContentViewHeader(desc);
165    }
166
167    @Override
168    public Set<String> getContentViewNames() {
169        return Collections.unmodifiableSet(contentViewReg.getContentViewNames());
170    }
171
172    @Override
173    public Set<ContentViewHeader> getContentViewHeaders() {
174        Set<ContentViewHeader> res = new HashSet<ContentViewHeader>();
175        for (ContentViewDescriptor desc : contentViewReg.getContentViews()) {
176            res.add(getContentViewHeader(desc));
177        }
178        return Collections.unmodifiableSet(res);
179    }
180
181    @Override
182    public Set<String> getContentViewNames(String flag) {
183        Set<String> res = new LinkedHashSet<String>();
184        Set<String> items = contentViewReg.getContentViewsByFlag(flag);
185        if (items != null) {
186            res.addAll(items);
187        }
188        return res;
189    }
190
191    @Override
192    public Set<ContentViewHeader> getContentViewHeaders(String flag) {
193        Set<String> cvs = getContentViewNames(flag);
194        Set<ContentViewHeader> res = new HashSet<ContentViewHeader>();
195        for (String cv : cvs) {
196            ContentViewHeader header = getContentViewHeader(cv);
197            if (header != null) {
198                res.add(header);
199            }
200        }
201        return Collections.unmodifiableSet(res);
202    }
203
204    @Override
205    public PageProvider<?> getPageProvider(String name, List<SortInfo> sortInfos, Long pageSize, Long currentPage,
206            DocumentModel searchDocument, Object... parameters) {
207        ContentViewDescriptor contentViewDesc = contentViewReg.getContentView(name);
208        if (contentViewDesc == null) {
209            return null;
210        }
211        PageProviderService ppService = Framework.getLocalService(PageProviderService.class);
212        String ppName = contentViewDesc.getPageProviderName();
213        PageProvider<?> provider = ppService.getPageProvider(ppName, searchDocument, sortInfos, pageSize, currentPage,
214                resolvePageProviderProperties(contentViewDesc.getPageProviderProperties()), parameters);
215        return provider;
216    }
217
218    public Map<String, Serializable> resolvePageProviderProperties(Map<String, String> stringProps)
219            {
220        // resolve properties
221        Map<String, Serializable> resolvedProps = new HashMap<String, Serializable>();
222        for (Map.Entry<String, String> prop : stringProps.entrySet()) {
223            resolvedProps.put(prop.getKey(), resolveProperty(prop.getValue()));
224        }
225        return resolvedProps;
226    }
227
228    protected Serializable resolveProperty(String elExpression) {
229        FacesContext context = FacesContext.getCurrentInstance();
230        Object value = ComponentTagUtils.resolveElExpression(context, elExpression);
231        if (value != null && !(value instanceof Serializable)) {
232            log.error(String.format("Error processing expression '%s', " + "result is not serializable: %s",
233                    elExpression, value));
234            return null;
235        }
236        return (Serializable) value;
237    }
238
239    @Override
240    @SuppressWarnings("unchecked")
241    public <T> T getAdapter(Class<T> adapter) {
242        if (adapter.isAssignableFrom(ContentViewService.class)) {
243            return (T) this;
244        }
245        return null;
246    }
247
248    @Override
249    public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
250        if (CONTENT_VIEW_EP.equals(extensionPoint)) {
251            ContentViewDescriptor desc = (ContentViewDescriptor) contribution;
252            contentViewReg.addContribution(desc);
253            registerPageProvider(desc);
254        }
255    }
256
257    protected void registerPageProvider(ContentViewDescriptor desc) {
258        ReferencePageProviderDescriptor refDesc = desc.getReferencePageProvider();
259        if (refDesc != null && refDesc.isEnabled()) {
260            // we use an already registered pp
261            return;
262        }
263        PageProviderService ppService = Framework.getLocalService(PageProviderService.class);
264        String name = desc.getName();
265        PageProviderDefinition coreDef = getPageProviderDefWithName(name, desc.getCoreQueryPageProvider());
266        PageProviderDefinition genDef = getPageProviderDefWithName(name, desc.getGenericPageProvider());
267        if (coreDef != null && genDef != null) {
268            log.error(String.format("Only one page provider should be registered on "
269                    + "content view '%s': take the reference descriptor by default, then core query descriptor, "
270                    + "and then generic descriptor", name));
271        }
272        PageProviderDefinition ppDef = (coreDef != null) ? coreDef : genDef;
273        if (ppDef != null) {
274            if (log.isDebugEnabled()) {
275                log.debug(String.format("Register PageProvider from ContentView: %s %s", ppDef.getName(), ppDef));
276            }
277            ppService.registerPageProviderDefinition(ppDef);
278        }
279    }
280
281    protected PageProviderDefinition getPageProviderDefWithName(String name, PageProviderDefinition ppDef) {
282        if (ppDef != null && ppDef.isEnabled()) {
283            if (ppDef.getName() == null) {
284                ppDef.setName(name);
285            }
286            return ppDef;
287        }
288        return null;
289    }
290
291    @Override
292    public void unregisterContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
293        if (CONTENT_VIEW_EP.equals(extensionPoint)) {
294            ContentViewDescriptor desc = (ContentViewDescriptor) contribution;
295            unregisterPageProvider(desc);
296            contentViewReg.removeContribution(desc);
297        }
298    }
299
300    protected void unregisterPageProvider(ContentViewDescriptor desc) {
301        PageProviderService ppService = Framework.getLocalService(PageProviderService.class);
302        if (ppService == null) {
303            log.info("PageProviderServer is not available, failed to unregister pp of the cv");
304            return;
305        }
306        if (desc.getCoreQueryPageProvider() != null) {
307
308            ppService.unregisterPageProviderDefinition(desc.getCoreQueryPageProvider());
309        }
310        if (desc.getGenericPageProvider() != null) {
311            ppService.unregisterPageProviderDefinition(desc.getGenericPageProvider());
312        }
313    }
314
315    @Override
316    public ContentView restoreContentView(ContentViewState contentViewState) {
317        if (contentViewState == null) {
318            return null;
319        }
320        String name = contentViewState.getContentViewName();
321        ContentView cv = getContentView(name);
322        if (cv != null) {
323            restoreContentViewState(cv, contentViewState);
324        } else {
325            throw new NuxeoException(String.format("Unknown content view with name '%s'", name));
326        }
327        return cv;
328    }
329
330    @Override
331    public void restoreContentViewState(ContentView contentView, ContentViewState contentViewState) {
332        if (contentView == null || contentViewState == null) {
333            return;
334        }
335
336        // save some info directly on content view, they will be needed
337        // when re-building the provider
338        Long pageSize = contentViewState.getPageSize();
339        contentView.setCurrentPageSize(pageSize);
340        DocumentModel searchDocument = contentViewState.getSearchDocumentModel();
341        contentView.setSearchDocumentModel(searchDocument);
342        if (searchDocument != null) {
343            // check that restored doc type is still in sync with doc type
344            // set on content view
345            String searchType = contentView.getSearchDocumentModelType();
346            if (!searchDocument.getType().equals(searchType)) {
347                log.warn(String.format("Restored document type '%s' is different from "
348                        + "the one declared on content view with name '%s': should be '%s'",
349                    searchDocument.getType(), contentViewState.getContentViewName(), searchType));
350            }
351        }
352        Long currentPage = contentViewState.getCurrentPage();
353        Object[] params = contentViewState.getQueryParameters();
354        // init page provider
355        contentView.setExecuted(true);
356        contentView.getPageProvider(searchDocument, contentViewState.getSortInfos(), pageSize, currentPage, params);
357        // restore rendering info, unless bindings are present on content
358        // view configuration
359        if (!contentView.hasResultLayoutBinding()) {
360            contentView.setCurrentResultLayout(contentViewState.getResultLayout());
361        }
362        if (!contentView.hasResultLayoutColumnsBinding()) {
363            contentView.setCurrentResultLayoutColumns(contentViewState.getResultColumns());
364        }
365    }
366
367    @Override
368    public ContentViewState saveContentView(ContentView contentView) {
369        if (contentView == null) {
370            return null;
371        }
372        ContentViewState state = new ContentViewStateImpl();
373        state.setContentViewName(contentView.getName());
374        state.setPageSize(contentView.getCurrentPageSize());
375        // provider info
376        PageProvider<?> pp = contentView.getCurrentPageProvider();
377        if (pp != null) {
378            state.setPageProviderName(pp.getName());
379            state.setSearchDocumentModel(pp.getSearchDocumentModel());
380            state.setCurrentPage(new Long(pp.getCurrentPageIndex()));
381            state.setQueryParameters(pp.getParameters());
382            state.setSortInfos(pp.getSortInfos());
383        } else {
384            // take at least info available on content view
385            state.setSearchDocumentModel(contentView.getSearchDocumentModel());
386            state.setQueryParameters(contentView.getQueryParameters());
387        }
388        // rendering info
389        state.setResultLayout(contentView.getCurrentResultLayout());
390        state.setResultColumns(contentView.getCurrentResultLayoutColumns());
391        return state;
392    }
393
394}