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.List;
019import java.util.Locale;
020import java.util.Optional;
021
022import org.apache.cocoon.components.ContextHelper;
023import org.apache.cocoon.environment.Request;
024import org.apache.cocoon.xml.AttributesImpl;
025import org.apache.cocoon.xml.XMLUtils;
026import org.apache.commons.lang3.StringUtils;
027import org.slf4j.Logger;
028import org.xml.sax.ContentHandler;
029import org.xml.sax.SAXException;
030
031import org.ametys.cms.contenttype.ContentType;
032import org.ametys.odf.program.Program;
033import org.ametys.odf.program.ProgramPart;
034import org.ametys.odf.program.SubProgram;
035import org.ametys.odf.program.SubProgramFactory;
036import org.ametys.plugins.odfweb.repository.ProgramPage;
037import org.ametys.plugins.odfweb.service.search.ProgramReturnable.DisplaySubprogramMode;
038import org.ametys.plugins.repository.AmetysObject;
039import org.ametys.runtime.model.View;
040import org.ametys.web.frontoffice.search.metamodel.ReturnableSaxer;
041import org.ametys.web.frontoffice.search.metamodel.impl.PageReturnable;
042import org.ametys.web.frontoffice.search.metamodel.impl.PageSaxer;
043import org.ametys.web.frontoffice.search.requesttime.SearchComponentArguments;
044import org.ametys.web.repository.page.Page;
045import org.ametys.web.repository.site.Site;
046
047/**
048 * {@link ReturnableSaxer} for {@link ProgramReturnable}
049 */
050public class ProgramSaxer extends PageSaxer
051{
052    private ProgramReturnable _programReturnable;
053    private DisplaySubprogramMode _displaySubprogramMode;
054
055    /**
056     * Constructor
057     * @param pageReturnable The page returnable (needed for superclass)
058     * @param programReturnable The associated returnable on programs
059     * @param displaySubprogramMode The mode for displaying (or not) subprograms
060     */
061    public ProgramSaxer(PageReturnable pageReturnable, ProgramReturnable programReturnable, DisplaySubprogramMode displaySubprogramMode)
062    {
063        super(pageReturnable);
064        _programReturnable = programReturnable;
065        _displaySubprogramMode = displaySubprogramMode;
066    }
067    
068    @Override
069    public boolean canSax(AmetysObject hit, Logger logger, SearchComponentArguments args)
070    {
071        return hit instanceof Program;
072    }
073    
074    @Override
075    public void sax(ContentHandler contentHandler, AmetysObject hit, Logger logger, SearchComponentArguments args) throws SAXException
076    {
077        Request request = ContextHelper.getRequest(_programReturnable._avalonContext);
078        try
079        {
080            Program program = (Program) hit;
081            Optional<ProgramPage> programPage = _resolveProgramPage(program, args.currentSite());
082            if (programPage.isPresent())
083            {
084                _saxProgramPage(contentHandler, programPage.get(), logger, args);
085            }
086            else
087            {
088                logger.warn("The program {} was returned from the search but its ProgramPage could not be resolved", program);
089                // sax minimal data for the program
090                String sitemapName = args.currentPage().getSitemapName();
091                _saxProgramWithNoPage(contentHandler, program, sitemapName, logger);
092            }
093            saxSubPrograms(contentHandler, program, programPage, logger, request);
094        }
095        finally
096        {
097            MatchingSubprogramSearchComponent._removeMatchingSubProgramIdsRequestAttribute(request);
098        }
099    }
100    
101    private Optional<ProgramPage> _resolveProgramPage(Program program, Site currentSite)
102    {
103        return Optional.of(_programReturnable._getOdfPageResolver())
104                .map(res -> res.getProgramPage(program, currentSite.getName()));
105    }
106    
107    private Optional<Page> _resolveSubProgramPage(Program program, Optional<ProgramPage> programPage, SubProgram subProgram)
108    {
109        return programPage
110                .map(ProgramPage::getSiteName)
111                .map(siteName -> _programReturnable._getOdfPageResolver().getSubProgramPage(subProgram, program, siteName));
112    }
113    
114    private String _getSubProgramPagePath(Page subProgramPage, String programPath)
115    {
116        return StringUtils.substringAfterLast(subProgramPage.getPathInSitemap(), programPath);
117    }
118    
119    private void _saxProgramWithNoPage(ContentHandler contentHandler, Program program, String sitemapName, Logger logger) throws SAXException
120    {
121        Locale locale = new Locale(sitemapName);
122        XMLUtils.createElement(contentHandler, "title", program.getTitle());
123        saxContent(program, "index", locale, contentHandler, logger);
124    }
125    
126    private void _saxProgramPage(ContentHandler contentHandler, ProgramPage programPage, Logger logger, SearchComponentArguments args) throws SAXException
127    {
128        super.sax(contentHandler, programPage, logger, args);
129    }
130    
131    /**
132     * SAX the subprograms of the program
133     * @param contentHandler The content handler
134     * @param program The program
135     * @param programPage The program page
136     * @param logger A logger
137     * @param request The request
138     * @throws SAXException if a SAX error occured
139     */
140    protected void saxSubPrograms(ContentHandler contentHandler, Program program, Optional<ProgramPage> programPage, Logger logger, Request request) throws SAXException
141    {
142        if (_displaySubprogramMode == DisplaySubprogramMode.NONE)
143        {
144            return;
145        }
146
147        List<String> matchingSubProgramIds = MatchingSubprogramSearchComponent._getMatchingSubProgramIds(request);
148        
149        ContentType subProgramCType = _programReturnable._getContentTypeEP().getExtension(SubProgramFactory.SUBPROGRAM_CONTENT_TYPE);
150        View view = subProgramCType.getView("index");
151        Optional<String> programPath = programPage
152                .map(ProgramPage::getPathInSitemap);
153        
154        for (ProgramPart childProgramPart : program.getProgramPartChildren())
155        {
156            if (childProgramPart instanceof SubProgram)
157            {
158                SubProgram subProgram = (SubProgram) childProgramPart;
159                boolean matchSearch = matchingSubProgramIds.contains(subProgram.getId());
160                saxSubProgram(contentHandler, subProgram, program, programPage, matchSearch, programPath, logger, view);
161            }
162        }
163    }
164    
165    /**
166     * SAX the subprogram
167     * @param contentHandler The content handler
168     * @param subProgram The subProgram
169     * @param program The program
170     * @param programPage The program page
171     * @param matchSearch <code>true</code> if the subprogram matches the search
172     * @param programPath The program path
173     * @param logger A logger
174     * @param view The view
175     * @throws SAXException if a SAX error occured
176     */
177    protected void saxSubProgram(
178            ContentHandler contentHandler, 
179            SubProgram subProgram, 
180            Program program, 
181            Optional<ProgramPage> programPage, 
182            boolean matchSearch, 
183            Optional<String> programPath, 
184            Logger logger, 
185            View view) throws SAXException
186    {
187        if (_displaySubprogramMode != DisplaySubprogramMode.MATCHING_SEARCH_ONLY || matchSearch)
188        {
189            AttributesImpl attrs = new AttributesImpl();
190            Optional<Page> subProgramPage = _resolveSubProgramPage(program, programPage, subProgram);
191            if (subProgramPage.isPresent() && programPath.isPresent())
192            {
193                attrs.addCDATAAttribute("path", _getSubProgramPagePath(subProgramPage.get(), programPath.get()));
194            }
195            attrs.addCDATAAttribute("id", subProgram.getId());
196            attrs.addCDATAAttribute("name", subProgram.getName());
197            attrs.addCDATAAttribute("title", subProgram.getTitle());
198            if (_displaySubprogramMode == DisplaySubprogramMode.ALL_WITH_HIGHLIGHT)
199            {
200                attrs.addCDATAAttribute("highlight", String.valueOf(matchSearch));
201            }
202            XMLUtils.startElement(contentHandler, "subprogram", attrs);
203
204            try
205            {
206                subProgram.dataToSAX(contentHandler, view);
207            }
208            catch (Exception e)
209            {
210                logger.error("An error occurred during saxing subprogram '" + subProgram.getId() + "' metadata", e);
211            }
212            
213            XMLUtils.endElement(contentHandler, "subprogram");
214        }
215    }
216}