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