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