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.plugins.odfweb.service.search;
017
018import java.util.Arrays;
019import java.util.Collection;
020import java.util.Collections;
021import java.util.Map;
022import java.util.Optional;
023
024import org.apache.avalon.framework.service.ServiceException;
025import org.apache.avalon.framework.service.ServiceManager;
026
027import org.ametys.cms.search.advanced.AbstractTreeNode;
028import org.ametys.cms.search.query.AndQuery;
029import org.ametys.cms.search.query.ConstantNilScoreQuery;
030import org.ametys.cms.search.query.ContentTypeQuery;
031import org.ametys.cms.search.query.IsolateQuery;
032import org.ametys.cms.search.query.JoinQuery;
033import org.ametys.cms.search.query.MaxScoreOrQuery;
034import org.ametys.cms.search.query.Query;
035import org.ametys.odf.program.Program;
036import org.ametys.odf.program.ProgramFactory;
037import org.ametys.odf.program.SubProgramFactory;
038import org.ametys.odf.program.TraversableProgramPart;
039import org.ametys.plugins.odfweb.service.search.criterion.DegreeUniversityAttributeContentSearchCriterionDefinition;
040import org.ametys.plugins.odfweb.service.search.helper.DegreeUniversityHelper;
041import org.ametys.web.frontoffice.search.instance.model.FOSearchCriterion;
042import org.ametys.web.frontoffice.search.metamodel.AdditionalParameterValueMap;
043import org.ametys.web.frontoffice.search.metamodel.Returnable;
044import org.ametys.web.frontoffice.search.metamodel.SearchCriterionDefinition;
045import org.ametys.web.frontoffice.search.metamodel.Searchable;
046import org.ametys.web.frontoffice.search.metamodel.impl.AbstractContentBasedSearchable;
047
048/**
049 * {@link Searchable} for {@link Program}s
050 */
051public class ProgramSearchable extends AbstractContentBasedSearchable
052{
053    /** Avalon Role */
054    public static final String ROLE = ProgramSearchable.class.getName();
055    
056    /** The additional parameter for indicating if search has to be made also on subprograms */
057    public static final String PARAMETER_SEARCH_ON_SUBPROGRAMS = "searchSubprogram";
058    
059    /** The prefix for program searchable */
060    public static final String CRITERION_DEFINITIONS_PREFIX_ID = "ProgramSearchable$";
061
062    /** The prefix for indexing field in program searcheable */
063    public static final String PROGRAM_SEARCHEABLE_INDEXING_FIELD_PREFIX = CRITERION_DEFINITIONS_PREFIX_ID + "indexingField$" + ProgramFactory.PROGRAM_CONTENT_TYPE + "$";
064    
065    /** The degree university helper */
066    protected DegreeUniversityHelper _degreeUniversityHelper;
067    
068    @Override
069    public void service(ServiceManager manager) throws ServiceException
070    {
071        super.service(manager);
072        _degreeUniversityHelper = (DegreeUniversityHelper) manager.lookup(DegreeUniversityHelper.ROLE);
073    }
074    
075    @Override
076    protected String associatedContentReturnableRole()
077    {
078        return ProgramReturnable.ROLE;
079    }
080    
081    @Override
082    protected String getCriterionDefinitionPrefix()
083    {
084        return CRITERION_DEFINITIONS_PREFIX_ID;
085    }
086    
087    @Override
088    public Collection<Returnable> relationsWith()
089    {
090        return Arrays.asList(_associatedContentReturnable);
091    }
092    
093    @Override
094    protected Collection<String> getContentTypes(AdditionalParameterValueMap additionalParameterValues)
095    {
096        return Collections.singleton(ProgramFactory.PROGRAM_CONTENT_TYPE);
097    }
098    
099    @Override
100    public Optional<Query> joinQuery(Query queryOnCriterion, SearchCriterionDefinition criterion, Collection<Returnable> returnables, AdditionalParameterValueMap additionalParameters)
101    {
102        if (returnables.contains(_associatedContentReturnable))
103        {
104            return Optional.of(queryOnCriterion);
105        }
106        return Optional.empty();
107    }
108    
109    @Override
110    public Query buildQuery(
111            AbstractTreeNode<FOSearchCriterion> criterionTree, 
112            Map<String, Object> userCriteria, 
113            Collection<Returnable> returnables,
114            Collection<Searchable> searchables, 
115            AdditionalParameterValueMap additionalParameters, 
116            String currentLang, 
117            Map<String, Object> contextualParameters)
118    {
119        Query query = super.buildQuery(criterionTree, userCriteria, returnables, searchables, additionalParameters, currentLang, contextualParameters);
120        if (_searchOnSubprograms(additionalParameters))
121        {
122            // This query search on
123            //   - programs matching user criteria
124            //   OR
125            //   - subprograms matching user criteria
126            // This is the same query because it is assumed that programs and subprograms have the same attributes (properties are added for subprogram to match with missing program attributes)
127            return new MaxScoreOrQuery(
128                        new IsolateQuery(query), 
129                        getProgramThroughSubprogramsQuery(query)
130                    );
131        }
132        else
133        {
134            return query;
135        }
136    }
137    
138    private boolean _searchOnSubprograms(AdditionalParameterValueMap additionalParameters)
139    {
140        return additionalParameters.getValue(PARAMETER_SEARCH_ON_SUBPROGRAMS);
141    }
142    
143    /**
144     * Gets the {@link Query} that returns Programs by querying their SubPrograms
145     * @param queryOnCriterion The query on SubPrograms
146     * @return The {@link Query} that returns Programs
147     */
148    protected Query getProgramThroughSubprogramsQuery(Query queryOnCriterion)
149    {
150        Query queryOnSubprograms = new AndQuery(
151            new ConstantNilScoreQuery(new ContentTypeQuery(SubProgramFactory.SUBPROGRAM_CONTENT_TYPE)),
152            queryOnCriterion
153        );
154        return new ProgramThroughProgramPartsQuery(queryOnSubprograms);
155    }
156    
157    @Override
158    public Collection<SearchCriterionDefinition> getCriteria(AdditionalParameterValueMap additionalParameterValues)
159    {
160        Collection<SearchCriterionDefinition> criteria = super.getCriteria(additionalParameterValues);
161        
162        DegreeUniversityAttributeContentSearchCriterionDefinition degreeUniversityCriterionDefinition = _degreeUniversityHelper.getDegreeUniversityCriterionDefinition(this);
163        if (degreeUniversityCriterionDefinition != null)
164        {
165            criteria.add(degreeUniversityCriterionDefinition);
166        }
167        
168        return criteria;
169    }
170
171    /**
172     * A {@link Query} that returns Programs, by querying their child ProgramParts
173     */
174    protected static class ProgramThroughProgramPartsQuery extends JoinQuery
175    {
176        /**
177         * Builds a ProgramThroughProgramPartsQuery
178         * @param subQuery The query on ProgramParts
179         */
180        protected ProgramThroughProgramPartsQuery(Query subQuery)
181        {
182            super(subQuery, TraversableProgramPart.CHILD_PROGRAM_PARTS);
183        }
184    }
185}