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}