001/*
002 *  Copyright 2013 Anyware Services
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 */
016package org.ametys.cms.search.ui.model;
017
018import java.util.Collection;
019import java.util.Collections;
020import java.util.HashSet;
021import java.util.LinkedHashMap;
022import java.util.Map;
023import java.util.Map.Entry;
024import java.util.Set;
025
026import org.apache.avalon.framework.configuration.DefaultConfiguration;
027import org.apache.avalon.framework.context.Context;
028import org.apache.avalon.framework.service.ServiceManager;
029import org.apache.commons.lang3.StringUtils;
030import org.slf4j.Logger;
031
032import org.ametys.cms.contenttype.ContentType;
033import org.ametys.cms.contenttype.ContentTypeEnumerator;
034import org.ametys.cms.contenttype.ContentTypeExtensionPoint;
035import org.ametys.cms.contenttype.MetadataType;
036import org.ametys.cms.search.query.Query;
037import org.ametys.cms.search.query.Query.Operator;
038import org.ametys.cms.search.ui.model.impl.AbstractSearchUICriterion;
039import org.ametys.cms.search.ui.model.impl.SystemSearchUICriterion;
040import org.ametys.runtime.i18n.I18nizableText;
041import org.ametys.runtime.model.ModelViewItem;
042import org.ametys.runtime.model.ViewItemContainer;
043import org.ametys.runtime.parameter.Enumerator;
044import org.ametys.runtime.parameter.Validator;
045import org.ametys.runtime.plugin.component.ThreadSafeComponentManager;
046
047/**
048 * Dynamic search model wrapping a search model with a restriction on content types
049 *
050 */
051public class DynamicWrappedSearchUIModel implements SearchUIModel
052{
053    private ThreadSafeComponentManager<Enumerator> _enumeratorManager;
054    
055    private Logger _logger;
056    private SearchUIModel _wrappedModel;
057    private Set<String> _restrictedContentTypes;
058    private Set<String> _restrictedMixins;
059    private boolean _containsReferenceTable;
060    
061    /**
062     * Constructor
063     * @param model The wrapped search model
064     * @param restrictedContentTypes The restricted content types
065     * @param cTypeEP The content type extension point
066     * @param logger The logger
067     * @param context The context
068     * @param manager The service manager
069     */
070    public DynamicWrappedSearchUIModel(SearchUIModel model, Collection<String> restrictedContentTypes, ContentTypeExtensionPoint cTypeEP, Logger logger, Context context, ServiceManager manager)
071    {
072        _wrappedModel = model;
073        
074        _restrictedContentTypes = new HashSet<>();
075        _restrictedMixins = new HashSet<>();
076        
077        for (String cTypeId : restrictedContentTypes)
078        {
079            ContentType cType = cTypeEP.getExtension(cTypeId);
080            if (cType.isMixin())
081            {
082                _restrictedMixins.add(cTypeId);
083            }
084            else
085            {
086                _restrictedContentTypes.add(cTypeId);
087            }
088            
089            _containsReferenceTable = cType.isReferenceTable() || _containsReferenceTable;
090            
091            for (String subTypeId : cTypeEP.getSubTypes(cTypeId))
092            {
093                ContentType subCType = cTypeEP.getExtension(subTypeId);
094                if (subCType.isMixin())
095                {
096                    _restrictedMixins.add(subTypeId);
097                }
098                else
099                {
100                    _restrictedContentTypes.add(subTypeId);
101                }
102                
103                _containsReferenceTable = subCType.isReferenceTable() || _containsReferenceTable;
104            }
105        }
106        
107        try
108        {
109            _logger = logger;
110            _enumeratorManager = new ThreadSafeComponentManager<>();
111            _enumeratorManager.setLogger(logger);
112            _enumeratorManager.contextualize(context);
113            _enumeratorManager.service(manager);
114            
115            _addContentTypeEnumeratorComponent();
116            _addContentTypeAndMixinEnumeratorComponent();
117            _addMixinEnumeratorComponent();
118            
119            _enumeratorManager.initialize();
120        }
121        catch (Exception e)
122        {
123            _logger.error("Unable to instantiate enumerator for class: " + ContentTypeEnumerator.class.getName(), e);
124        }
125    }
126    
127    private void _addContentTypeEnumeratorComponent()
128    {
129        DefaultConfiguration conf = new DefaultConfiguration("criteria");
130        
131        DefaultConfiguration enumConf = new DefaultConfiguration("enumeration");
132        
133        DefaultConfiguration customEnumerator = new DefaultConfiguration("custom-enumerator");
134        customEnumerator.setAttribute("class", ContentTypeEnumerator.class.getName());
135        
136        if (!_restrictedContentTypes.isEmpty())
137        {
138            DefaultConfiguration cTypeConf = new DefaultConfiguration("strictContentTypes");
139            cTypeConf.setValue(StringUtils.join(_restrictedContentTypes, ","));
140            customEnumerator.addChild(cTypeConf);
141            
142            DefaultConfiguration excludeConf = new DefaultConfiguration("excludeMixin");
143            excludeConf.setValue(true);
144            customEnumerator.addChild(excludeConf);
145            
146            if (_containsReferenceTable)
147            {
148                DefaultConfiguration excludeRefTableConf = new DefaultConfiguration("excludeReferenceTable");
149                excludeRefTableConf.setValue(false);
150                customEnumerator.addChild(excludeRefTableConf);
151            }
152            
153            enumConf.addChild(customEnumerator);
154            conf.addChild(enumConf);
155            
156            _enumeratorManager.addComponent("cms", null, ContentTypeEnumerator.class.getName(), ContentTypeEnumerator.class, conf);
157        }
158    }
159    
160    private void _addContentTypeAndMixinEnumeratorComponent()
161    {
162        DefaultConfiguration conf = new DefaultConfiguration("criteria");
163        
164        DefaultConfiguration enumConf = new DefaultConfiguration("enumeration");
165        
166        DefaultConfiguration customEnumerator = new DefaultConfiguration("custom-enumerator");
167        customEnumerator.setAttribute("class", ContentTypeEnumerator.class.getName());
168        
169        if (_restrictedContentTypes.size() > 0)
170        {
171            DefaultConfiguration cTypeConf = new DefaultConfiguration("strictContentTypes");
172            cTypeConf.setValue(StringUtils.join(_restrictedContentTypes, ","));
173            customEnumerator.addChild(cTypeConf);
174            
175            if (_containsReferenceTable)
176            {
177                DefaultConfiguration excludeRefTableConf = new DefaultConfiguration("excludeReferenceTable");
178                excludeRefTableConf.setValue(false);
179                customEnumerator.addChild(excludeRefTableConf);
180            }
181            
182            enumConf.addChild(customEnumerator);
183            conf.addChild(enumConf);
184            
185            _enumeratorManager.addComponent("cms", null, ContentTypeEnumerator.class.getName() + ".ContentTypeAndMixin", ContentTypeEnumerator.class, conf);
186        }
187    }
188    
189    private void _addMixinEnumeratorComponent()
190    {
191        DefaultConfiguration conf = new DefaultConfiguration("criteria");
192        
193        DefaultConfiguration enumConf = new DefaultConfiguration("enumeration");
194        
195        DefaultConfiguration customEnumerator = new DefaultConfiguration("custom-enumerator");
196        customEnumerator.setAttribute("class", ContentTypeEnumerator.class.getName());
197        
198        if (_restrictedMixins.size() > 0)
199        {
200            DefaultConfiguration cTypeConf = new DefaultConfiguration("strictContentTypes");
201            cTypeConf.setValue(StringUtils.join(_restrictedMixins, ","));
202            customEnumerator.addChild(cTypeConf);
203            
204            DefaultConfiguration excludeConf = new DefaultConfiguration("excludeMixin");
205            excludeConf.setValue(false);
206            customEnumerator.addChild(excludeConf);
207            
208            enumConf.addChild(customEnumerator);
209            conf.addChild(enumConf);
210            
211            _enumeratorManager.addComponent("cms", null, ContentTypeEnumerator.class.getName() + ".Mixin", ContentTypeEnumerator.class, conf);
212        }
213    }
214
215    @Override
216    public Set<String> getContentTypes(Map<String, Object> contextualParameters)
217    {
218        return _restrictedContentTypes;
219    }
220    
221    @Override
222    public Set<String> getExcludedContentTypes(Map<String, Object> contextualParameters)
223    {
224        return Collections.<String>emptySet();
225    }
226    
227    @Override
228    public String getSearchUrl(Map<String, Object> contextualParameters)
229    {
230        return _wrappedModel.getSearchUrl(contextualParameters);
231    }
232
233    @Override
234    public String getSearchUrlPlugin(Map<String, Object> contextualParameters)
235    {
236        return _wrappedModel.getSearchUrlPlugin(contextualParameters);
237    }
238
239    @Override
240    public String getExportCSVUrl(Map<String, Object> contextualParameters)
241    {
242        return _wrappedModel.getExportCSVUrl(contextualParameters);
243    }
244
245    @Override
246    public String getExportCSVUrlPlugin(Map<String, Object> contextualParameters)
247    {
248        return _wrappedModel.getExportCSVUrlPlugin(contextualParameters);
249    }
250
251    @Override
252    public String getExportDOCUrl(Map<String, Object> contextualParameters)
253    {
254        return _wrappedModel.getExportDOCUrl(contextualParameters);
255    }
256
257    @Override
258    public String getExportDOCUrlPlugin(Map<String, Object> contextualParameters)
259    {
260        return _wrappedModel.getExportDOCUrlPlugin(contextualParameters);
261    }
262
263    @Override
264    public String getExportXMLUrl(Map<String, Object> contextualParameters)
265    {
266        return _wrappedModel.getExportXMLUrl(contextualParameters);
267    }
268
269    @Override
270    public String getExportXMLUrlPlugin(Map<String, Object> contextualParameters)
271    {
272        return _wrappedModel.getExportXMLUrlPlugin(contextualParameters);
273    }
274
275    @Override
276    public String getExportPDFUrl(Map<String, Object> contextualParameters)
277    {
278        return _wrappedModel.getExportPDFUrl(contextualParameters);
279    }
280
281    @Override
282    public String getExportPDFUrlPlugin(Map<String, Object> contextualParameters)
283    {
284        return _wrappedModel.getExportPDFUrlPlugin(contextualParameters);
285    }
286
287    @Override
288    public String getPrintUrl(Map<String, Object> contextualParameters)
289    {
290        return _wrappedModel.getPrintUrl(contextualParameters);
291    }
292
293    @Override
294    public String getPrintUrlPlugin(Map<String, Object> contextualParameters)
295    {
296        return _wrappedModel.getPrintUrlPlugin(contextualParameters);
297    }
298
299    @Override
300    public String getSummaryView()
301    {
302        return _wrappedModel.getSummaryView();
303    }
304
305    @Override
306    public Map<String, SearchUICriterion> getCriteria(Map<String, Object> contextualParameters)
307    {
308        Map<String, SearchUICriterion> criteria = _wrappedModel.getCriteria(contextualParameters);
309        
310        Map<String, SearchUICriterion> wrappedCriteria = new LinkedHashMap<>(criteria.size());
311        
312        for (Entry<String, SearchUICriterion> entry : criteria.entrySet())
313        {
314            SearchUICriterion sc = entry.getValue();
315            wrappedCriteria.put(entry.getKey(), wrap(sc));
316        }
317        
318        return wrappedCriteria;
319    }
320    
321    @Override
322    public SearchUICriterion getCriterion(String id, Map<String, Object> contextualParameters)
323    {
324        SearchUICriterion criteria = _wrappedModel.getCriterion(id, contextualParameters);
325        
326        return wrap(criteria);
327    }
328    
329    @Override
330    public Map<String, SearchUICriterion> getFacetedCriteria(Map<String, Object> contextualParameters)
331    {
332        return _wrappedModel.getFacetedCriteria(contextualParameters);
333    }
334    
335    @Override
336    public Map<String, SearchUICriterion> getAdvancedCriteria(Map<String, Object> contextualParameters)
337    {
338        return _wrappedModel.getAdvancedCriteria(contextualParameters);
339    }
340    
341    public ViewItemContainer getResultItems(Map<String, Object> contextualParameters)
342    {
343        return _wrappedModel.getResultItems(contextualParameters);
344    }
345    
346    public ModelViewItem getResultItem(String itemPath, Map<String, Object> contextualParameters)
347    {
348        return _wrappedModel.getResultItem(itemPath, contextualParameters);
349    }
350    
351    @Override
352    public int getPageSize(Map<String, Object> contextualParameters)
353    {
354        return _wrappedModel.getPageSize(contextualParameters);
355    }
356    
357    @Override
358    public String getWorkspace(Map<String, Object> contextualParameters)
359    {
360        return _wrappedModel.getWorkspace(contextualParameters);
361    }
362    
363    /**
364     * Wrap an existing search criterion.
365     * @param criterion the search criterion to wrap.
366     * @return the wrapped search criterion.
367     */
368    protected SearchUICriterion wrap(SearchUICriterion criterion)
369    {
370        try
371        {
372            if (criterion instanceof SystemSearchUICriterion systemSearchUICriterion
373                    && !_restrictedContentTypes.isEmpty() 
374                    && !systemSearchUICriterion.isJoined())
375            {
376                // Wrap "contentTypes" and "contentTypeOrMixin" system criteria, replacing their standard enumerator with the local one.
377                Enumerator enumerator;
378                switch (systemSearchUICriterion.getSystemPropertyId())
379                {
380                    case "contentTypes":
381                        enumerator = _enumeratorManager.lookup(ContentTypeEnumerator.class.getName());
382                        return new WrappedSearchUICriterion(criterion, enumerator);
383                    case "contentTypeOrMixin":
384                        enumerator = _enumeratorManager.lookup(ContentTypeEnumerator.class.getName() + ".ContentTypeAndMixin");
385                        return new WrappedSearchUICriterion(criterion, enumerator);
386                    case "mixins":
387                        enumerator = _enumeratorManager.lookup(ContentTypeEnumerator.class.getName() + ".Mixin");
388                        return new WrappedSearchUICriterion(criterion, enumerator);
389                    default:
390                        break;
391                }
392            }
393            
394            return criterion;
395        }
396        catch (Exception e)
397        {
398            _logger.error("Unable to lookup enumerator role.", e);
399            throw new RuntimeException("Unable to lookup enumerator role.", e);
400        }
401    }
402    
403    /**
404     * Search criteria wrapper.
405     */
406    protected class WrappedSearchUICriterion extends AbstractSearchUICriterion
407    {
408        
409        /** The wrapped search criteria. */
410        protected SearchUICriterion _wrappedCriterion;
411        
412        /**
413         * Build a search criteria wrapper.
414         * @param wrappedCriteria wrapped search criteria.
415         * @param enumerator the new enumerator.
416         */
417        public WrappedSearchUICriterion(SearchUICriterion wrappedCriteria, Enumerator enumerator)
418        {
419            _wrappedCriterion = wrappedCriteria;
420            setEnumerator(enumerator);
421        }
422        
423        @Override
424        public Enumerator getEnumerator()
425        {
426            return super.getEnumerator();
427        }
428        
429        @Override
430        public String getId()
431        {
432            return _wrappedCriterion.getId();
433        }
434        
435        @Override
436        public I18nizableText getLabel()
437        {
438            return _wrappedCriterion.getLabel();
439        }
440        
441        @Override
442        public I18nizableText getDescription()
443        {
444            return _wrappedCriterion.getDescription();
445        }
446        
447        @Override
448        public String getFieldId()
449        {
450            return _wrappedCriterion.getFieldId();
451        }
452        
453        @Override
454        public String getInitClassName()
455        {
456            return _wrappedCriterion.getInitClassName();
457        }
458        
459        @Override
460        public String getSubmitClassName()
461        {
462            return _wrappedCriterion.getSubmitClassName();
463        }
464        
465        @Override
466        public String getChangeClassName()
467        {
468            return _wrappedCriterion.getChangeClassName();
469        }
470        
471        @Override
472        public boolean isHidden()
473        {
474            return _wrappedCriterion.isHidden();
475        }
476        
477        @Override
478        public MetadataType getType()
479        {
480            return _wrappedCriterion.getType();
481        }
482        
483        @Override
484        public boolean isMultiple()
485        {
486            return _wrappedCriterion.isMultiple();
487        }
488        
489        @Override
490        public String getWidget()
491        {
492            return _wrappedCriterion.getWidget();
493        }
494        
495//        @Override
496//        public Object getValue()
497//        {
498//            return _wrappedCriterion.getValue();
499//        }
500        
501        @Override
502        public Map<String, I18nizableText> getWidgetParameters()
503        {
504            return _wrappedCriterion.getWidgetParameters();
505        }
506        
507        @Override
508        public Validator getValidator()
509        {
510            return _wrappedCriterion.getValidator();
511        }
512        
513        @Override
514        public Object getDefaultValue()
515        {
516            return _wrappedCriterion.getDefaultValue();
517        }
518        
519        @Override
520        public String getContentTypeId()
521        {
522            return _wrappedCriterion.getContentTypeId();
523        }
524        
525        @Override
526        public Operator getOperator()
527        {
528            return _wrappedCriterion.getOperator();
529        }
530        
531        @Override
532        public Query getQuery(Object value, Operator customOperator, Map<String, Object> allValues, String language, Map<String, Object> contextualParameters)
533        {
534            return _wrappedCriterion.getQuery(value, customOperator, allValues, language, contextualParameters);
535        }
536    }
537}