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.contenttype.ContentType;
030import org.ametys.cms.contenttype.ContentTypeExtensionPoint;
031import org.ametys.cms.contenttype.MetadataType;
032import org.ametys.cms.repository.Content;
033import org.ametys.cms.repository.ContentTypeExpression;
034import org.ametys.cms.repository.LanguageExpression;
035import org.ametys.cms.search.ui.model.SearchUICriterion;
036import org.ametys.core.util.LambdaUtils;
037import org.ametys.plugins.repository.AmetysObjectIterable;
038import org.ametys.plugins.repository.AmetysObjectResolver;
039import org.ametys.plugins.repository.RepositoryConstants;
040import org.ametys.plugins.repository.query.QueryHelper;
041import org.ametys.plugins.repository.query.expression.AndExpression;
042import org.ametys.plugins.repository.query.expression.Expression;
043import org.ametys.plugins.repository.query.expression.OrExpression;
044import org.ametys.plugins.repository.query.expression.Expression.Operator;
045import org.ametys.runtime.i18n.I18nizableText;
046import org.ametys.web.frontoffice.search.metamodel.FrontEnumerableSearchCriterionDefinition;
047import org.ametys.web.frontoffice.search.metamodel.Searchable;
048
049/**
050 * {@link ContentSearchCriterionDefinition} for a {@link MetadataType#CONTENT} attribute.
051 */
052public class ContentAttributeContentSearchCriterionDefinition extends ContentSearchCriterionDefinition implements FrontEnumerableSearchCriterionDefinition
053{
054    private static final Logger __LOGGER = LoggerFactory.getLogger(ContentAttributeContentSearchCriterionDefinition.class);
055    
056    /** The resolver */
057    protected AmetysObjectResolver _resolver;
058    /** The extension point for content types */
059    protected ContentTypeExtensionPoint _cTypeEP;
060    
061    /**
062     * Default constructor
063     * @param id The id
064     * @param pluginName The plugin name
065     * @param searchable the {@link Searchable}
066     * @param criterion The linked {@link SearchUICriterion}
067     * @param contentType The content type on which this criterion definition applies. Can be empty if it applies to all types of contents.
068     * @param resolver The resolver
069     * @param contentTypeEP The extension point for content types
070     */
071    public ContentAttributeContentSearchCriterionDefinition(
072            String id,
073            String pluginName,
074            Optional<Searchable> searchable,
075            SearchUICriterion criterion,
076            Optional<ContentType> contentType,
077            AmetysObjectResolver resolver,
078            ContentTypeExtensionPoint contentTypeEP)
079    {
080        super(id, pluginName, searchable, criterion, contentType);
081        _resolver = resolver;
082        _cTypeEP = contentTypeEP;
083    }
084    
085    @Override
086    public Map<Object, I18nizableText> getEntries(String language)
087    {
088        String cTypeId = _searchUICriterion.getContentTypeId();
089        if (cTypeId != null)
090        {
091            try
092            {
093                boolean multilingual = _cTypeEP.getExtension(cTypeId).isMultilingual();
094                Expression expr = new AndExpression(
095                        getContentTypeExpression(cTypeId),
096                        multilingual ? null : new LanguageExpression(Operator.EQ, language));
097                AmetysObjectIterable<Content> contents = _resolver.query(QueryHelper.getXPathQuery(null, RepositoryConstants.NAMESPACE_PREFIX + ":content", expr));
098                
099                return contents.stream()
100                        .map(content -> Pair.of(content.getId(), content.getTitle(new Locale(language))))
101                        .sorted(Comparator.comparing(Pair::getRight)) // sort by title
102                        .collect(LambdaUtils.Collectors.toLinkedHashMap(Pair::getLeft, pair -> new I18nizableText(pair.getRight())));
103            }
104            catch (Exception e)
105            {
106                __LOGGER.error("Failed to get content enumeration for content type {}", cTypeId, e);
107                return Collections.EMPTY_MAP;
108            }
109        }
110        
111        return Collections.EMPTY_MAP;
112    }
113    
114    /**
115     * Gets the content type expression for retrieving enumeration of contents
116     * @param parentCTypeId The parent content type id
117     * @return The {@link Expression}
118     */
119    protected Expression getContentTypeExpression(String parentCTypeId)
120    {
121        Stream<String> subCTypesIds = _cTypeEP.getSubTypes(parentCTypeId).stream();
122        Expression[] exprs = Stream.concat(Stream.of(parentCTypeId), subCTypesIds)
123                .map(cTypeId -> new ContentTypeExpression(Operator.EQ, cTypeId))
124                .toArray(Expression[]::new);
125        return new OrExpression(exprs);
126    }
127}
128