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("strictContentTypes");
131            cTypeConf.setValue(StringUtils.join(_restrictedContentTypes, ","));
132            customEnumerator.addChild(cTypeConf);
133            
134            DefaultConfiguration excludeConf = new DefaultConfiguration("excludeMixin");
135            excludeConf.setValue(true);
136            customEnumerator.addChild(excludeConf);
137            
138            enumConf.addChild(customEnumerator);
139            conf.addChild(enumConf);
140            
141            _enumeratorManager.addComponent("cms", null, ContentTypeEnumerator.class.getName(), ContentTypeEnumerator.class, conf);
142        }
143    }
144    
145    private void _addContentTypeAndMixinEnumeratorComponent()
146    {
147        DefaultConfiguration conf = new DefaultConfiguration("criteria");
148        
149        DefaultConfiguration enumConf = new DefaultConfiguration("enumeration");
150        
151        DefaultConfiguration customEnumerator = new DefaultConfiguration("custom-enumerator");
152        customEnumerator.setAttribute("class", ContentTypeEnumerator.class.getName());
153        
154        if (_restrictedContentTypes.size() > 0)
155        {
156            DefaultConfiguration cTypeConf = new DefaultConfiguration("strictContentTypes");
157            cTypeConf.setValue(StringUtils.join(_restrictedContentTypes, ","));
158            customEnumerator.addChild(cTypeConf);
159            
160            enumConf.addChild(customEnumerator);
161            conf.addChild(enumConf);
162            
163            _enumeratorManager.addComponent("cms", null, ContentTypeEnumerator.class.getName() + ".ContentTypeAndMixin", ContentTypeEnumerator.class, conf);
164        }
165    }
166    
167    private void _addMixinEnumeratorComponent()
168    {
169        DefaultConfiguration conf = new DefaultConfiguration("criteria");
170        
171        DefaultConfiguration enumConf = new DefaultConfiguration("enumeration");
172        
173        DefaultConfiguration customEnumerator = new DefaultConfiguration("custom-enumerator");
174        customEnumerator.setAttribute("class", ContentTypeEnumerator.class.getName());
175        
176        if (_restrictedMixins.size() > 0)
177        {
178            DefaultConfiguration cTypeConf = new DefaultConfiguration("strictContentTypes");
179            cTypeConf.setValue(StringUtils.join(_restrictedMixins, ","));
180            customEnumerator.addChild(cTypeConf);
181            
182            DefaultConfiguration excludeConf = new DefaultConfiguration("excludeMixin");
183            excludeConf.setValue(false);
184            customEnumerator.addChild(excludeConf);
185            
186            enumConf.addChild(customEnumerator);
187            conf.addChild(enumConf);
188            
189            _enumeratorManager.addComponent("cms", null, ContentTypeEnumerator.class.getName() + ".Mixin", ContentTypeEnumerator.class, conf);
190        }
191    }
192
193    @Override
194    public Set<String> getContentTypes(Map<String, Object> contextualParameters)
195    {
196        return _restrictedContentTypes;
197    }
198    
199    @Override
200    public Set<String> getExcludedContentTypes(Map<String, Object> contextualParameters)
201    {
202        return Collections.<String>emptySet();
203    }
204    
205    @Override
206    public String getSearchUrl(Map<String, Object> contextualParameters)
207    {
208        return _wrappedModel.getSearchUrl(contextualParameters);
209    }
210
211    @Override
212    public String getSearchUrlPlugin(Map<String, Object> contextualParameters)
213    {
214        return _wrappedModel.getSearchUrlPlugin(contextualParameters);
215    }
216
217    @Override
218    public String getExportCSVUrl(Map<String, Object> contextualParameters)
219    {
220        return _wrappedModel.getExportCSVUrl(contextualParameters);
221    }
222
223    @Override
224    public String getExportCSVUrlPlugin(Map<String, Object> contextualParameters)
225    {
226        return _wrappedModel.getExportCSVUrlPlugin(contextualParameters);
227    }
228
229    @Override
230    public String getExportDOCUrl(Map<String, Object> contextualParameters)
231    {
232        return _wrappedModel.getExportDOCUrl(contextualParameters);
233    }
234
235    @Override
236    public String getExportDOCUrlPlugin(Map<String, Object> contextualParameters)
237    {
238        return _wrappedModel.getExportDOCUrlPlugin(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 String getSummaryView()
267    {
268        return _wrappedModel.getSummaryView();
269    }
270
271    @Override
272    public Map<String, SearchUICriterion> getCriteria(Map<String, Object> contextualParameters)
273    {
274        Map<String, SearchUICriterion> criteria = _wrappedModel.getCriteria(contextualParameters);
275        
276        Map<String, SearchUICriterion> wrappedCriteria = new LinkedHashMap<>(criteria.size());
277        
278        for (Entry<String, SearchUICriterion> entry : criteria.entrySet())
279        {
280            SearchUICriterion sc = entry.getValue();
281            wrappedCriteria.put(entry.getKey(), wrap(sc));
282        }
283        
284        return wrappedCriteria;
285    }
286    
287    @Override
288    public SearchUICriterion getCriterion(String id, Map<String, Object> contextualParameters)
289    {
290        SearchUICriterion criteria = _wrappedModel.getCriterion(id, contextualParameters);
291        
292        return wrap(criteria);
293    }
294    
295    @Override
296    public Map<String, SearchUICriterion> getFacetedCriteria(Map<String, Object> contextualParameters)
297    {
298        return _wrappedModel.getFacetedCriteria(contextualParameters);
299    }
300    
301    @Override
302    public Map<String, SearchUICriterion> getAdvancedCriteria(Map<String, Object> contextualParameters)
303    {
304        return _wrappedModel.getAdvancedCriteria(contextualParameters);
305    }
306    
307    @Override
308    public Map<String, SearchUIColumn> getResultFields(Map<String, Object> contextualParameters)
309    {
310        return _wrappedModel.getResultFields(contextualParameters);
311    }
312    
313    @Override
314    public SearchUIColumn getResultField(String id, Map<String, Object> contextualParameters)
315    {
316        return _wrappedModel.getResultField(id, contextualParameters);
317    }
318    
319    @Override
320    public int getPageSize(Map<String, Object> contextualParameters)
321    {
322        return _wrappedModel.getPageSize(contextualParameters);
323    }
324    
325    @Override
326    public String getWorkspace(Map<String, Object> contextualParameters)
327    {
328        return _wrappedModel.getWorkspace(contextualParameters);
329    }
330    
331    /**
332     * Wrap an existing search criteria.
333     * @param criteria the search criteria to wrap.
334     * @return the wrapped search criteria.
335     */
336    protected SearchUICriterion wrap(SearchUICriterion criteria)
337    {
338        try
339        {
340            if (criteria instanceof SystemSearchUICriterion && !_restrictedContentTypes.isEmpty())
341            {
342                SystemSearchUICriterion systemCriterion = (SystemSearchUICriterion) criteria;
343                
344                // Wrap "contentTypes" and "contentTypeOrMixin" system criteria, replacing their standard enumerator with the local one.
345                Enumerator enumerator;
346                switch (systemCriterion.getSystemPropertyId())
347                {
348                    case "contentTypes":
349                        enumerator = _enumeratorManager.lookup(ContentTypeEnumerator.class.getName());
350                        return new WrappedSearchUICriterion(systemCriterion, enumerator);
351                    case "contentTypeOrMixin":
352                        enumerator = _enumeratorManager.lookup(ContentTypeEnumerator.class.getName() + ".ContentTypeAndMixin");
353                        return new WrappedSearchUICriterion(systemCriterion, enumerator);
354                    case "mixins":
355                        enumerator = _enumeratorManager.lookup(ContentTypeEnumerator.class.getName() + ".Mixin");
356                        return new WrappedSearchUICriterion(systemCriterion, enumerator);
357                    default:
358                        break;
359                }
360            }
361            
362            return criteria;
363        }
364        catch (Exception e)
365        {
366            _logger.error("Unable to lookup enumerator role.", e);
367            throw new RuntimeException("Unable to lookup enumerator role.", e);
368        }
369    }
370    
371    /**
372     * Search criteria wrapper.
373     */
374    protected class WrappedSearchUICriterion extends AbstractSearchUICriterion
375    {
376        
377        /** The wrapped search criteria. */
378        protected SearchUICriterion _wrappedCriterion;
379        
380        /**
381         * Build a search criteria wrapper.
382         * @param wrappedCriteria wrapped search criteria.
383         * @param enumerator the new enumerator.
384         */
385        public WrappedSearchUICriterion(SearchUICriterion wrappedCriteria, Enumerator enumerator)
386        {
387            _wrappedCriterion = wrappedCriteria;
388            setEnumerator(enumerator);
389        }
390        
391        @Override
392        public Enumerator getEnumerator()
393        {
394            return super.getEnumerator();
395        }
396        
397        @Override
398        public String getId()
399        {
400            return _wrappedCriterion.getId();
401        }
402        
403        @Override
404        public I18nizableText getLabel()
405        {
406            return _wrappedCriterion.getLabel();
407        }
408        
409        @Override
410        public I18nizableText getDescription()
411        {
412            return _wrappedCriterion.getDescription();
413        }
414        
415        @Override
416        public String getFieldId()
417        {
418            return _wrappedCriterion.getFieldId();
419        }
420        
421        @Override
422        public String getInitClassName()
423        {
424            return _wrappedCriterion.getInitClassName();
425        }
426        
427        @Override
428        public String getSubmitClassName()
429        {
430            return _wrappedCriterion.getSubmitClassName();
431        }
432        
433        @Override
434        public String getChangeClassName()
435        {
436            return _wrappedCriterion.getChangeClassName();
437        }
438        
439        @Override
440        public boolean isHidden()
441        {
442            return _wrappedCriterion.isHidden();
443        }
444        
445        @Override
446        public MetadataType getType()
447        {
448            return _wrappedCriterion.getType();
449        }
450        
451        @Override
452        public boolean isMultiple()
453        {
454            return _wrappedCriterion.isMultiple();
455        }
456        
457        @Override
458        public String getWidget()
459        {
460            return _wrappedCriterion.getWidget();
461        }
462        
463//        @Override
464//        public Object getValue()
465//        {
466//            return _wrappedCriterion.getValue();
467//        }
468        
469        @Override
470        public Map<String, I18nizableText> getWidgetParameters()
471        {
472            return _wrappedCriterion.getWidgetParameters();
473        }
474        
475        @Override
476        public Validator getValidator()
477        {
478            return _wrappedCriterion.getValidator();
479        }
480        
481        @Override
482        public Object getDefaultValue()
483        {
484            return _wrappedCriterion.getDefaultValue();
485        }
486        
487        @Override
488        public String getContentTypeId()
489        {
490            return _wrappedCriterion.getContentTypeId();
491        }
492        
493        @Override
494        public Operator getOperator()
495        {
496            return _wrappedCriterion.getOperator();
497        }
498        
499        @Override
500        public Query getQuery(Object value, Operator customOperator, Map<String, Object> allValues, String language, Map<String, Object> contextualParameters)
501        {
502            return _wrappedCriterion.getQuery(value, customOperator, allValues, language, contextualParameters);
503        }
504        
505    }
506    
507}