001/*
002 *  Copyright 2019 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.Collections;
019import java.util.Comparator;
020import java.util.Locale;
021import java.util.Map;
022import java.util.Optional;
023import java.util.stream.Stream;
024
025import org.apache.commons.lang3.tuple.Pair;
026import org.slf4j.Logger;
027import org.slf4j.LoggerFactory;
028
029import org.ametys.cms.content.ContentHelper;
030import org.ametys.cms.contenttype.ContentType;
031import org.ametys.cms.contenttype.ContentTypeExtensionPoint;
032import org.ametys.cms.contenttype.MetadataType;
033import org.ametys.cms.repository.Content;
034import org.ametys.cms.repository.ContentTypeExpression;
035import org.ametys.cms.repository.LanguageExpression;
036import org.ametys.cms.search.ui.model.SearchUICriterion;
037import org.ametys.core.util.LambdaUtils;
038import org.ametys.plugins.repository.AmetysObjectIterable;
039import org.ametys.plugins.repository.AmetysObjectResolver;
040import org.ametys.plugins.repository.RepositoryConstants;
041import org.ametys.plugins.repository.query.QueryHelper;
042import org.ametys.plugins.repository.query.expression.AndExpression;
043import org.ametys.plugins.repository.query.expression.Expression;
044import org.ametys.plugins.repository.query.expression.Expression.Operator;
045import org.ametys.plugins.repository.query.expression.OrExpression;
046import org.ametys.runtime.i18n.I18nizableText;
047import org.ametys.runtime.parameter.Validator;
048import org.ametys.web.frontoffice.search.metamodel.FrontEnumerableSearchCriterionDefinition;
049import org.ametys.web.frontoffice.search.metamodel.Searchable;
050
051/**
052 * {@link ContentSearchCriterionDefinition} for a {@link MetadataType#CONTENT} attribute.
053 */
054public class ContentAttributeContentSearchCriterionDefinition extends ContentSearchCriterionDefinition implements FrontEnumerableSearchCriterionDefinition
055{
056    private static final Logger __LOGGER = LoggerFactory.getLogger(ContentAttributeContentSearchCriterionDefinition.class);
057    
058    /** The resolver */
059    protected AmetysObjectResolver _resolver;
060    /** The extension point for content types */
061    protected ContentTypeExtensionPoint _cTypeEP;
062    /** The content helper */
063    protected ContentHelper _contentHelper;
064    
065    /**
066     * Default constructor
067     * @param id The id
068     * @param pluginName The plugin name
069     * @param searchable the {@link Searchable}
070     * @param criterion The linked {@link SearchUICriterion}
071     * @param contentType The content type on which this criterion definition applies. Can be empty if it applies to all types of contents.
072     * @param validator the validator
073     * @param resolver The resolver
074     * @param contentTypeEP The extension point for content types
075     * @param contentHelper the content helper
076     */
077    public ContentAttributeContentSearchCriterionDefinition(
078            String id,
079            String pluginName,
080            Optional<Searchable> searchable,
081            SearchUICriterion criterion,
082            Optional<ContentType> contentType,
083            Optional<Validator> validator,
084            AmetysObjectResolver resolver,
085            ContentTypeExtensionPoint contentTypeEP,
086            ContentHelper contentHelper)
087    {
088        super(id, pluginName, searchable, criterion, contentType, validator);
089        _resolver = resolver;
090        _cTypeEP = contentTypeEP;
091        _contentHelper = contentHelper;
092    }
093    
094    @Override
095    public Map<Object, I18nizableText> getEntries(String language)
096    {
097        String cTypeId = _searchUICriterion.getContentTypeId();
098        if (cTypeId != null)
099        {
100            try
101            {
102                boolean multilingual = _cTypeEP.getExtension(cTypeId).isMultilingual();
103                Expression expr = new AndExpression(
104                        getContentTypeExpression(cTypeId),
105                        multilingual ? null : new LanguageExpression(Operator.EQ, language));
106                AmetysObjectIterable<Content> contents = _resolver.query(QueryHelper.getXPathQuery(null, RepositoryConstants.NAMESPACE_PREFIX + ":content", expr));
107                
108                return contents.stream()
109                        .map(content -> Pair.of(content.getId(), content.getTitle(new Locale(language))))
110                        .sorted(Comparator.comparing(Pair::getRight)) // sort by title
111                        .collect(LambdaUtils.Collectors.toLinkedHashMap(Pair::getLeft, pair -> new I18nizableText(pair.getRight())));
112            }
113            catch (Exception e)
114            {
115                __LOGGER.error("Failed to get content enumeration for content type {}", cTypeId, e);
116                return Collections.EMPTY_MAP;
117            }
118        }
119        
120        return Collections.EMPTY_MAP;
121    }
122    
123    /**
124     * Gets the content type expression for retrieving enumeration of contents
125     * @param parentCTypeId The parent content type id
126     * @return The {@link Expression}
127     */
128    protected Expression getContentTypeExpression(String parentCTypeId)
129    {
130        Stream<String> subCTypesIds = _cTypeEP.getSubTypes(parentCTypeId).stream();
131        Expression[] exprs = Stream.concat(Stream.of(parentCTypeId), subCTypesIds)
132                .map(cTypeId -> new ContentTypeExpression(Operator.EQ, cTypeId))
133                .toArray(Expression[]::new);
134        return new OrExpression(exprs);
135    }
136    
137
138    /**
139     * Get the order of the content id value for a content attribute search criterion 
140     * @param contentId the content id
141     * @return the order or null if there is no order
142     */
143    public Long getOrder(String contentId)
144    {
145        try
146        {
147            Content content = _resolver.resolveById(contentId);
148            if (_contentHelper.isReferenceTable(content) && content.hasDefinition("order") && content.hasValue("order"))
149            {
150                return content.<Long>getValue("order");
151            }
152        }
153        catch (Exception e) 
154        {
155            __LOGGER.warn("Can't get order value for criterion {} and value {}", getId(), contentId, e);
156        }
157        
158        return null;
159    }
160}
161