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.getService(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        if (coreDesc != null && coreDesc.getQuickFilters() != null) {
153            contentView.setQuickFilters(coreDesc.getQuickFilters());
154        }
155        return contentView;
156    }
157
158    protected ContentViewHeader getContentViewHeader(ContentViewDescriptor desc) {
159        return new ContentViewHeader(desc.getName(), desc.getTitle(), Boolean.TRUE.equals(desc.getTranslateTitle()),
160                desc.getIconPath());
161    }
162
163    @Override
164    public ContentViewHeader getContentViewHeader(String name) {
165        ContentViewDescriptor desc = contentViewReg.getContentView(name);
166        if (desc == null) {
167            return null;
168        }
169        return getContentViewHeader(desc);
170    }
171
172    @Override
173    public Set<String> getContentViewNames() {
174        return Collections.unmodifiableSet(contentViewReg.getContentViewNames());
175    }
176
177    @Override
178    public Set<ContentViewHeader> getContentViewHeaders() {
179        Set<ContentViewHeader> res = new HashSet<ContentViewHeader>();
180        for (ContentViewDescriptor desc : contentViewReg.getContentViews()) {
181            res.add(getContentViewHeader(desc));
182        }
183        return Collections.unmodifiableSet(res);
184    }
185
186    @Override
187    public Set<String> getContentViewNames(String flag) {
188        Set<String> res = new LinkedHashSet<String>();
189        Set<String> items = contentViewReg.getContentViewsByFlag(flag);
190        if (items != null) {
191            res.addAll(items);
192        }
193        return res;
194    }
195
196    @Override
197    public Set<ContentViewHeader> getContentViewHeaders(String flag) {
198        Set<String> cvs = getContentViewNames(flag);
199        Set<ContentViewHeader> res = new HashSet<ContentViewHeader>();
200        for (String cv : cvs) {
201            ContentViewHeader header = getContentViewHeader(cv);
202            if (header != null) {
203                res.add(header);
204            }
205        }
206        return Collections.unmodifiableSet(res);
207    }
208
209    @Override
210    public PageProvider<?> getPageProvider(String name, List<SortInfo> sortInfos, Long pageSize, Long currentPage,
211            DocumentModel searchDocument, Object... parameters) {
212        ContentViewDescriptor contentViewDesc = contentViewReg.getContentView(name);
213        if (contentViewDesc == null) {
214            return null;
215        }
216        PageProviderService ppService = Framework.getService(PageProviderService.class);
217        String ppName = contentViewDesc.getPageProviderName();
218        PageProvider<?> provider = ppService.getPageProvider(ppName, searchDocument, sortInfos, pageSize, currentPage,
219                resolvePageProviderProperties(contentViewDesc.getPageProviderProperties()), parameters);
220        return provider;
221    }
222
223    public Map<String, Serializable> resolvePageProviderProperties(Map<String, String> stringProps) {
224        // resolve properties
225        Map<String, Serializable> resolvedProps = new HashMap<String, Serializable>();
226        for (Map.Entry<String, String> prop : stringProps.entrySet()) {
227            resolvedProps.put(prop.getKey(), resolveProperty(prop.getValue()));
228        }
229        return resolvedProps;
230    }
231
232    protected Serializable resolveProperty(String elExpression) {
233        FacesContext context = FacesContext.getCurrentInstance();
234        Object value = ComponentTagUtils.resolveElExpression(context, elExpression);
235        if (value != null && !(value instanceof Serializable)) {
236            log.error("Error processing expression '" + elExpression + "', result is not serializable: " + value);
237            return null;
238        }
239        return (Serializable) value;
240    }
241
242    @Override
243    @SuppressWarnings("unchecked")
244    public <T> T getAdapter(Class<T> adapter) {
245        if (adapter.isAssignableFrom(ContentViewService.class)) {
246            return (T) this;
247        }
248        return null;
249    }
250
251    @Override
252    public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
253        if (CONTENT_VIEW_EP.equals(extensionPoint)) {
254            ContentViewDescriptor desc = (ContentViewDescriptor) contribution;
255            contentViewReg.addContribution(desc);
256            registerPageProvider(desc);
257        }
258    }
259
260    protected void registerPageProvider(ContentViewDescriptor desc) {
261        ReferencePageProviderDescriptor refDesc = desc.getReferencePageProvider();
262        if (refDesc != null && refDesc.isEnabled()) {
263            // we use an already registered pp
264            return;
265        }
266        PageProviderService ppService = Framework.getService(PageProviderService.class);
267        String name = desc.getName();
268        PageProviderDefinition coreDef = getPageProviderDefWithName(name, desc.getCoreQueryPageProvider());
269        PageProviderDefinition genDef = getPageProviderDefWithName(name, desc.getGenericPageProvider());
270        if (coreDef != null && genDef != null) {
271            log.error(String.format("Only one page provider should be registered on "
272                    + "content view '%s': take the reference descriptor by default, then core query descriptor, "
273                    + "and then generic descriptor", name));
274        }
275        PageProviderDefinition ppDef = (coreDef != null) ? coreDef : genDef;
276        if (ppDef != null) {
277            if (log.isDebugEnabled()) {
278                log.debug(String.format("Register PageProvider from ContentView: %s %s", ppDef.getName(), ppDef));
279            }
280            ppService.registerPageProviderDefinition(ppDef);
281        }
282    }
283
284    protected PageProviderDefinition getPageProviderDefWithName(String name, PageProviderDefinition ppDef) {
285        if (ppDef != null && ppDef.isEnabled()) {
286            if (ppDef.getName() == null) {
287                ppDef.setName(name);
288            }
289            return ppDef;
290        }
291        return null;
292    }
293
294    @Override
295    public void unregisterContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
296        if (CONTENT_VIEW_EP.equals(extensionPoint)) {
297            ContentViewDescriptor desc = (ContentViewDescriptor) contribution;
298            unregisterPageProvider(desc);
299            contentViewReg.removeContribution(desc);
300        }
301    }
302
303    protected void unregisterPageProvider(ContentViewDescriptor desc) {
304        PageProviderService ppService = Framework.getService(PageProviderService.class);
305        if (ppService == null) {
306            log.info("PageProviderServer is not available, failed to unregister pp of the cv");
307            return;
308        }
309        if (desc.getCoreQueryPageProvider() != null) {
310
311            ppService.unregisterPageProviderDefinition(desc.getCoreQueryPageProvider());
312        }
313        if (desc.getGenericPageProvider() != null) {
314            ppService.unregisterPageProviderDefinition(desc.getGenericPageProvider());
315        }
316    }
317
318    @Override
319    public ContentView restoreContentView(ContentViewState contentViewState) {
320        if (contentViewState == null) {
321            return null;
322        }
323        String name = contentViewState.getContentViewName();
324        ContentView cv = getContentView(name);
325        if (cv != null) {
326            restoreContentViewState(cv, contentViewState);
327        } else {
328            throw new NuxeoException("Unknown content view with name '" + name + "'");
329        }
330        return cv;
331    }
332
333    @Override
334    public void restoreContentViewState(ContentView contentView, ContentViewState contentViewState) {
335        if (contentView == null || contentViewState == null) {
336            return;
337        }
338
339        // save some info directly on content view, they will be needed
340        // when re-building the provider
341        Long pageSize = contentViewState.getPageSize();
342        contentView.setCurrentPageSize(pageSize);
343        DocumentModel searchDocument = contentViewState.getSearchDocumentModel();
344        contentView.setSearchDocumentModel(searchDocument);
345        if (searchDocument != null) {
346            // check that restored doc type is still in sync with doc type
347            // set on content view
348            String searchType = contentView.getSearchDocumentModelType();
349            if (!searchDocument.getType().equals(searchType)) {
350                log.warn(String.format(
351                        "Restored document type '%s' is different from "
352                                + "the one declared on content view with name '%s': should be '%s'",
353                        searchDocument.getType(), contentViewState.getContentViewName(), searchType));
354            }
355        }
356        Long currentPage = contentViewState.getCurrentPage();
357        Object[] params = contentViewState.getQueryParameters();
358
359        // init page provider
360        contentView.setExecuted(contentViewState.isExecuted());
361        contentView.getPageProvider(searchDocument, contentViewState.getSortInfos(), pageSize, currentPage, params);
362        // restore rendering info, unless bindings are present on content
363        // view configuration
364        if (!contentView.hasResultLayoutBinding()) {
365            contentView.setCurrentResultLayout(contentViewState.getResultLayout());
366        }
367        if (!contentView.hasResultLayoutColumnsBinding()) {
368            contentView.setCurrentResultLayoutColumns(contentViewState.getResultColumns());
369        }
370    }
371
372    @Override
373    public ContentViewState saveContentView(ContentView contentView) {
374        if (contentView == null) {
375            return null;
376        }
377        ContentViewState state = new ContentViewStateImpl();
378        state.setContentViewName(contentView.getName());
379        state.setPageSize(contentView.getCurrentPageSize());
380        // provider info
381        PageProvider<?> pp = contentView.getCurrentPageProvider();
382        if (pp != null) {
383            state.setPageProviderName(pp.getName());
384            state.setSearchDocumentModel(pp.getSearchDocumentModel());
385            state.setCurrentPage(new Long(pp.getCurrentPageIndex()));
386            state.setQueryParameters(pp.getParameters());
387            state.setSortInfos(pp.getSortInfos());
388        } else {
389            // take at least info available on content view
390            state.setSearchDocumentModel(contentView.getSearchDocumentModel());
391            state.setQueryParameters(contentView.getQueryParameters());
392        }
393        // rendering info
394        state.setResultLayout(contentView.getCurrentResultLayout());
395        state.setResultColumns(contentView.getCurrentResultLayoutColumns());
396        state.setExecuted(contentView.isExecuted());
397        return state;
398    }
399
400}