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.jsf;
020
021import java.io.Serializable;
022import java.util.ArrayList;
023import java.util.Arrays;
024import java.util.Collections;
025import java.util.HashMap;
026import java.util.HashSet;
027import java.util.LinkedHashSet;
028import java.util.List;
029import java.util.Map;
030import java.util.Set;
031
032import javax.faces.context.FacesContext;
033
034import org.apache.commons.logging.Log;
035import org.apache.commons.logging.LogFactory;
036import org.nuxeo.ecm.core.api.DocumentModel;
037import org.nuxeo.ecm.core.api.NuxeoException;
038import org.nuxeo.ecm.core.api.SortInfo;
039import org.nuxeo.ecm.platform.query.api.PageProvider;
040import org.nuxeo.ecm.platform.query.api.PageProviderDefinition;
041import org.nuxeo.ecm.platform.query.api.PageProviderService;
042import org.nuxeo.ecm.platform.query.core.CoreQueryPageProviderDescriptor;
043import org.nuxeo.ecm.platform.query.core.GenericPageProviderDescriptor;
044import org.nuxeo.ecm.platform.query.core.ReferencePageProviderDescriptor;
045import org.nuxeo.ecm.platform.ui.web.util.ComponentTagUtils;
046import org.nuxeo.runtime.api.Framework;
047import org.nuxeo.runtime.model.ComponentInstance;
048import org.nuxeo.runtime.model.DefaultComponent;
049
050/**
051 * @author Anahide Tchertchian
052 * @since 5.4
053 */
054public class ContentViewServiceImpl extends DefaultComponent implements ContentViewService {
055
056    public static final String CONTENT_VIEW_EP = "contentViews";
057
058    private static final long serialVersionUID = 1L;
059
060    private static final Log log = LogFactory.getLog(ContentViewServiceImpl.class);
061
062    protected ContentViewRegistry contentViewReg = new ContentViewRegistry();
063
064    @Override
065    public ContentView getContentView(String name) {
066        ContentViewDescriptor desc = contentViewReg.getContentView(name);
067        if (desc == null) {
068            return null;
069        }
070        Boolean useGlobalPageSize = desc.getUseGlobalPageSize();
071        if (useGlobalPageSize == null) {
072            useGlobalPageSize = Boolean.FALSE;
073        }
074        Boolean translateTitle = desc.getTranslateTitle();
075        if (translateTitle == null) {
076            translateTitle = Boolean.FALSE;
077        }
078        Boolean translateEmptySentence = desc.getTranslateEmptySentence();
079        if (translateEmptySentence == null) {
080            translateEmptySentence = Boolean.FALSE;
081        }
082        Boolean showTitle = desc.getShowTitle();
083        if (showTitle == null) {
084            showTitle = Boolean.FALSE;
085        }
086        Boolean showPageSizeSelector = desc.getShowPageSizeSelector();
087        if (showPageSizeSelector == null) {
088            showPageSizeSelector = Boolean.FALSE;
089        }
090        Boolean showRefreshPage = desc.getShowRefreshCommand();
091        if (showRefreshPage == null) {
092            showRefreshPage = Boolean.TRUE;
093        }
094        Boolean showFilterForm = desc.getShowFilterForm();
095        if (showFilterForm == null) {
096            showFilterForm = Boolean.FALSE;
097        }
098
099        String[] queryParams = null;
100        String searchDocumentType = null;
101        String sortInfosBinding = null;
102        String pageSizeBinding = null;
103        CoreQueryPageProviderDescriptor coreDesc = desc.getCoreQueryPageProvider();
104        GenericPageProviderDescriptor genDesc = desc.getGenericPageProvider();
105        ReferencePageProviderDescriptor refDesc = desc.getReferencePageProvider();
106        String[] refQueryParams = null;
107        if (refDesc != null && refDesc.isEnabled()) {
108            PageProviderService ppService = Framework.getLocalService(PageProviderService.class);
109            PageProviderDefinition def = ppService.getPageProviderDefinition(refDesc.getName());
110            if (def == null) {
111                log.error("Could not resolve page provider with name " + refDesc.getName());
112            } else if (def instanceof CoreQueryPageProviderDescriptor) {
113                coreDesc = (CoreQueryPageProviderDescriptor) def;
114                refQueryParams = refDesc.getQueryParameters();
115            } else if (def instanceof GenericPageProviderDescriptor) {
116                genDesc = (GenericPageProviderDescriptor) def;
117                refQueryParams = refDesc.getQueryParameters();
118            }
119        }
120        if (coreDesc != null && coreDesc.isEnabled()) {
121            queryParams = coreDesc.getQueryParameters();
122            sortInfosBinding = coreDesc.getSortInfosBinding();
123            pageSizeBinding = coreDesc.getPageSizeBinding();
124            searchDocumentType = coreDesc.getSearchDocumentType();
125        } else if (genDesc != null && genDesc.isEnabled()) {
126            queryParams = genDesc.getQueryParameters();
127            sortInfosBinding = genDesc.getSortInfosBinding();
128            pageSizeBinding = genDesc.getPageSizeBinding();
129            searchDocumentType = genDesc.getSearchDocumentType();
130        }
131        List<String> allQueryParams = new ArrayList<String>();
132        if (queryParams != null) {
133            allQueryParams.addAll(Arrays.asList(queryParams));
134        }
135        if (refQueryParams != null) {
136            allQueryParams.addAll(Arrays.asList(refQueryParams));
137        }
138        String searchDocBinding = desc.getSearchDocumentBinding();
139        ContentViewImpl contentView = new ContentViewImpl(name, desc.getTitle(), translateTitle.booleanValue(),
140                desc.getIconPath(), desc.getSelectionListName(), desc.getPagination(), desc.getActionCategories(),
141                desc.getSearchLayout(), desc.getResultLayouts(), desc.getFlags(), desc.getCacheKey(),
142                desc.getCacheSize(), desc.getRefreshEventNames(), desc.getResetEventNames(),
143                useGlobalPageSize.booleanValue(), allQueryParams.toArray(new String[] {}), searchDocBinding,
144                searchDocumentType, desc.getResultColumnsBinding(), desc.getResultLayoutBinding(), sortInfosBinding,
145                pageSizeBinding, showTitle.booleanValue(), showPageSizeSelector.booleanValue(),
146                showRefreshPage.booleanValue(), showFilterForm.booleanValue(), desc.getEmptySentence(),
147                translateEmptySentence.booleanValue());
148        contentView.setWaitForExecutionSentence(desc.getWaitForExecutionSentence());
149        if (desc.getWaitForExecution() != null) {
150            contentView.setWaitForExecution(desc.getWaitForExecution().booleanValue());
151        }
152        return contentView;
153    }
154
155    protected ContentViewHeader getContentViewHeader(ContentViewDescriptor desc) {
156        return new ContentViewHeader(desc.getName(), desc.getTitle(), Boolean.TRUE.equals(desc.getTranslateTitle()),
157                desc.getIconPath());
158    }
159
160    @Override
161    public ContentViewHeader getContentViewHeader(String name) {
162        ContentViewDescriptor desc = contentViewReg.getContentView(name);
163        if (desc == null) {
164            return null;
165        }
166        return getContentViewHeader(desc);
167    }
168
169    @Override
170    public Set<String> getContentViewNames() {
171        return Collections.unmodifiableSet(contentViewReg.getContentViewNames());
172    }
173
174    @Override
175    public Set<ContentViewHeader> getContentViewHeaders() {
176        Set<ContentViewHeader> res = new HashSet<ContentViewHeader>();
177        for (ContentViewDescriptor desc : contentViewReg.getContentViews()) {
178            res.add(getContentViewHeader(desc));
179        }
180        return Collections.unmodifiableSet(res);
181    }
182
183    @Override
184    public Set<String> getContentViewNames(String flag) {
185        Set<String> res = new LinkedHashSet<String>();
186        Set<String> items = contentViewReg.getContentViewsByFlag(flag);
187        if (items != null) {
188            res.addAll(items);
189        }
190        return res;
191    }
192
193    @Override
194    public Set<ContentViewHeader> getContentViewHeaders(String flag) {
195        Set<String> cvs = getContentViewNames(flag);
196        Set<ContentViewHeader> res = new HashSet<ContentViewHeader>();
197        for (String cv : cvs) {
198            ContentViewHeader header = getContentViewHeader(cv);
199            if (header != null) {
200                res.add(header);
201            }
202        }
203        return Collections.unmodifiableSet(res);
204    }
205
206    @Override
207    public PageProvider<?> getPageProvider(String name, List<SortInfo> sortInfos, Long pageSize, Long currentPage,
208            DocumentModel searchDocument, Object... parameters) {
209        ContentViewDescriptor contentViewDesc = contentViewReg.getContentView(name);
210        if (contentViewDesc == null) {
211            return null;
212        }
213        PageProviderService ppService = Framework.getLocalService(PageProviderService.class);
214        String ppName = contentViewDesc.getPageProviderName();
215        PageProvider<?> provider = ppService.getPageProvider(ppName, searchDocument, sortInfos, pageSize, currentPage,
216                resolvePageProviderProperties(contentViewDesc.getPageProviderProperties()), parameters);
217        return provider;
218    }
219
220    public Map<String, Serializable> resolvePageProviderProperties(Map<String, String> stringProps) {
221        // resolve properties
222        Map<String, Serializable> resolvedProps = new HashMap<String, Serializable>();
223        for (Map.Entry<String, String> prop : stringProps.entrySet()) {
224            resolvedProps.put(prop.getKey(), resolveProperty(prop.getValue()));
225        }
226        return resolvedProps;
227    }
228
229    protected Serializable resolveProperty(String elExpression) {
230        FacesContext context = FacesContext.getCurrentInstance();
231        Object value = ComponentTagUtils.resolveElExpression(context, elExpression);
232        if (value != null && !(value instanceof Serializable)) {
233            log.error("Error processing expression '" + elExpression + "', result is not serializable: " + 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("Unknown content view with name '" + 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(
348                        "Restored document type '%s' is different from "
349                                + "the one declared on content view with name '%s': should be '%s'",
350                        searchDocument.getType(), contentViewState.getContentViewName(), searchType));
351            }
352        }
353        Long currentPage = contentViewState.getCurrentPage();
354        Object[] params = contentViewState.getQueryParameters();
355
356        // init page provider
357        contentView.setExecuted(contentViewState.isExecuted());
358        contentView.getPageProvider(searchDocument, contentViewState.getSortInfos(), pageSize, currentPage, params);
359        // restore rendering info, unless bindings are present on content
360        // view configuration
361        if (!contentView.hasResultLayoutBinding()) {
362            contentView.setCurrentResultLayout(contentViewState.getResultLayout());
363        }
364        if (!contentView.hasResultLayoutColumnsBinding()) {
365            contentView.setCurrentResultLayoutColumns(contentViewState.getResultColumns());
366        }
367    }
368
369    @Override
370    public ContentViewState saveContentView(ContentView contentView) {
371        if (contentView == null) {
372            return null;
373        }
374        ContentViewState state = new ContentViewStateImpl();
375        state.setContentViewName(contentView.getName());
376        state.setPageSize(contentView.getCurrentPageSize());
377        // provider info
378        PageProvider<?> pp = contentView.getCurrentPageProvider();
379        if (pp != null) {
380            state.setPageProviderName(pp.getName());
381            state.setSearchDocumentModel(pp.getSearchDocumentModel());
382            state.setCurrentPage(new Long(pp.getCurrentPageIndex()));
383            state.setQueryParameters(pp.getParameters());
384            state.setSortInfos(pp.getSortInfos());
385        } else {
386            // take at least info available on content view
387            state.setSearchDocumentModel(contentView.getSearchDocumentModel());
388            state.setQueryParameters(contentView.getQueryParameters());
389        }
390        // rendering info
391        state.setResultLayout(contentView.getCurrentResultLayout());
392        state.setResultColumns(contentView.getCurrentResultLayoutColumns());
393        state.setExecuted(contentView.isExecuted());
394        return state;
395    }
396
397}