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