001/*
002 *  Copyright 2020 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.web.frontoffice.search.metamodel.impl;
017
018import java.util.ArrayList;
019import java.util.Collection;
020import java.util.HashMap;
021import java.util.List;
022import java.util.Map;
023import java.util.Objects;
024import java.util.stream.Collectors;
025
026import org.apache.avalon.framework.service.ServiceException;
027
028import org.ametys.cms.data.type.indexing.IndexableElementType;
029import org.ametys.cms.search.query.NotQuery;
030import org.ametys.cms.search.query.OrQuery;
031import org.ametys.cms.search.query.Query;
032import org.ametys.cms.search.query.Query.Operator;
033import org.ametys.plugins.repository.AmetysObjectResolver;
034import org.ametys.plugins.repository.UnknownAmetysObjectException;
035import org.ametys.runtime.i18n.I18nizableText;
036import org.ametys.runtime.model.type.ModelItemTypeConstants;
037import org.ametys.runtime.parameter.DefaultValidator;
038import org.ametys.runtime.parameter.Validator;
039import org.ametys.web.frontoffice.search.metamodel.RestrictedEnumerator;
040import org.ametys.web.frontoffice.search.metamodel.SearchServiceCriterionDefinition;
041import org.ametys.web.repository.page.Page;
042import org.ametys.web.search.query.PageQuery;
043
044/**
045 * {@link SearchServiceCriterionDefinition} proposing a criterion definition on the page of the indexed document.
046 */
047public class PageCriterionDefinition extends AbstractSearchServiceCriterionDefinition<String>
048{
049    /** The ametys object resolver */
050    private AmetysObjectResolver _resolver;
051    
052    @Override
053    protected boolean isTooBigForStaticEnumerator() 
054    {
055        return true;
056    }
057    
058    @Override
059    public RestrictedEnumerator<String> getRestrictedEnumerator(Map<String, Object> contextualParameters)
060    {
061        return new PageEnumeratedValues(_getAmetysObjectResolver());
062    }
063    
064    @Override
065    public String getWidget()
066    {
067        return "edition.select-page";
068    }
069    
070    @Override
071    public Map<String, I18nizableText> getWidgetParameters()
072    {
073        Map<String, I18nizableText> parameters = new HashMap<>();
074        parameters.put("siteContext", new I18nizableText("all"));
075        
076        return parameters;
077    }
078    
079    @SuppressWarnings("unchecked")
080    @Override
081    public IndexableElementType<String> getType()
082    {
083        String typeId = ModelItemTypeConstants.STRING_TYPE_ID;
084        return (IndexableElementType<String>) _getCriterionTypeExtensionPoint().getExtension(typeId);
085    }
086    
087    @Override
088    public Query getQuery(Object value, Operator operator, Map<String, Object> allValues, String language, Map<String, Object> contextualParameters)
089    {
090        String[] pageIds;
091        if (value instanceof String)
092        {
093            pageIds = ((String) value).split(",");
094        }
095        else if (value instanceof Collection<?>)
096        {
097            pageIds = ((Collection<?>) value)
098                    .stream()
099                    .filter(String.class::isInstance)
100                    .map(String.class::cast)
101                    .toArray(String[]::new);
102        }
103        else
104        {
105            throw new IllegalArgumentException("Not handled type of value: '" + value + "'");
106        }
107        
108        List<Query> queries = new ArrayList<>();
109        for (String pageId : pageIds)
110        {
111            queries.add(new PageQuery(pageId, true));
112        }
113        
114        OrQuery orQuery = new OrQuery(queries);
115        return operator == Operator.NE ? new NotQuery(orQuery) : orQuery;
116    }
117    
118    static class PageEnumeratedValues implements RestrictedEnumerator<String>
119    {
120        /** The ametys object resolver */
121        private AmetysObjectResolver _resolver;
122        
123        public PageEnumeratedValues(AmetysObjectResolver resolver)
124        {
125            this._resolver = resolver;
126        }
127        
128        public Map<String, I18nizableText> getEntries()  throws Exception
129        {
130            // Never calculate all values because too many page can be display
131            return new HashMap<>();
132        }
133
134        public RestrictedValues<String> getRestrictedEntriesFor(List<String> objs)
135        {
136            return new PageRestrictedValues(objs, this._resolver);
137        }
138        
139        static class PageRestrictedValues implements RestrictedValues<String>
140        {
141            private List<String> _forObjs;
142            private AmetysObjectResolver _resolver;
143            
144            PageRestrictedValues(List<String> forObjs, AmetysObjectResolver resolver)
145            {
146                this._forObjs = forObjs;
147                this._resolver = resolver;
148            }
149            
150            @Override
151            public Map<String, I18nizableText> values()
152            {
153                return this._forObjs.stream()
154                    .filter(String.class::isInstance)
155                    .map(String.class::cast)
156                    .map(this::_getPage)
157                    .filter(Objects::nonNull)
158                    .collect(Collectors.toMap(Page::getId, page -> new I18nizableText(page.getTitle())));
159            }
160            
161            private Page _getPage(String pageId)
162            {
163                try
164                {
165                    return this._resolver.resolveById(pageId);
166                }
167                catch (UnknownAmetysObjectException e) 
168                {
169                    return null;
170                }
171            }
172        }
173    }
174
175    @Override
176    public Validator getValidator()
177    {
178        return new DefaultValidator(null, true);
179    }
180    
181    @Override
182    public Query getEmptyValueQuery(String language, Map<String, Object> contextualParameters)
183    {
184        throw new UnsupportedOperationException("This method should not be called on the criteria PageCriterionDefinition");
185    }
186    
187    /**
188     * Retrieves the {@link AmetysObjectResolver}
189     * @return the {@link AmetysObjectResolver}
190     */
191    protected AmetysObjectResolver _getAmetysObjectResolver()
192    {
193        if (_resolver == null)
194        {
195            try
196            {
197                _resolver = (AmetysObjectResolver) __serviceManager.lookup(AmetysObjectResolver.ROLE);
198            }
199            catch (ServiceException e)
200            {
201                throw new RuntimeException("Unable to lookup after the ametys object resolver", e);
202            }
203        }
204        
205        return _resolver;
206    }
207}