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